React -ReduxToolkit-实现connect高阶组件-中间件的实现原理

227 阅读4分钟

ReduxToolkit (RTK)

核心API

  • configureStore: 包装createStore以提供简化的配置选项和良好的默认值。它可以自动组合你的 slice reducer,添加你提供的任何 Redux 中间件,redux-thunk默认包含,并启用 Redux DevTools Extension
    • reducer 可以将slice中的reducer组成一个对象 传入此处
    • middleware 可以使用除了默认 其他的中间件
    • devTools 是否配置devTools工具 默认为true
  • createSlice 接受reducer函数的对象、切片名称和初始状态值,并自动生成切片reducer,并带有相应的actions。
    • name :用户标记的slice名称(用于在redux- devtool中显示)
    • initialState: 第一次初始化值
    • reducers : 相当于之前的 reducer函数 他有两个参数
      • state
      • 调用这个action时 传递的action
    • createSlice 返回的是一个对象 包含着所有actions image.png
  • createAsyncThunk :接受一个动作类型字符串和一个返回承诺的函数,并生成一个pending/fulfilled/rejected基于该承诺分派动作类型的thunk
    • 第一个参数 为当前派发的名字
    • 一个异步事件

image.png

  • 当createAsyncThunk创建出来的action被dispatch时候 会有三种状态

    • pending
    • fulfilled
    • rejected
  • 可以在createSlice中的entraReducer的进行监听这三种结果

    • 对象形式
  extraReducers: {
    [fetchHomeMultidataAction.pending](state, action) {
      console.log("fetchHomeMultidataAction pending")
    },
    [fetchHomeMultidataAction.fulfilled](state, { payload }) {
      state.banners = payload.data.banner.list
      state.recommends = payload.data.recommend.list
    },
    [fetchHomeMultidataAction.rejected](state, action) {
      console.log("fetchHomeMultidataAction rejected")
    }
  }
  • 函数形式
  extraReducers: (builder) => {
    builder.addCase(fetchHomeMultidataAction.pending, (state, action) => {
      console.log("fetchHomeMultidataAction pending")
    }).addCase(fetchHomeMultidataAction.fulfilled, (state, { payload }) => {
      state.banners = payload.data.banner.list
      state.recommends = payload.data.recommend.list
    })
  }
  • 也可以通过createAsyncThunk函数中 通过参数来调用stroe的dispatch

export const fetchHomeMultidataAction = createAsyncThunk(
 "fetch/homemultidata", 
 async (extraInfo, { dispatch, getState }) => {
   // console.log(extraInfo, dispatch, getState)
   // 1.发送网络请求, 获取数据
   const res = await axios.get("http://123.207.32.32:8000/home/multidata")

   // 2.取出数据, 并且在此处直接dispatch操作(可以不做)
   const banners = res.data.data.banner.list
   const recommends = res.data.data.recommend.list
   dispatch(changeBanners(banners))
   dispatch(changeRecommends(recommends))

   // 3.返回结果, 那么action状态会变成fulfilled状态
   return res.data
})

关于React开发中 数据的不可变形 在Redux-Toolkit中如何实现的

在redux中的reducer函数中 需要返回一个新对象 这样在内部对比state进行比较是否修改的(浅比较) 但是在Redux-Toolkit中直接修改state就可以? 是这为什么呢?

在正常redux中的reducer

function reducer(state = defaultState, action) {
 switch (action.type) {
   case actionTypes.ADD_NUMBER:
     return { ...state, counter: state.counter + action.num }
   case actionTypes.SUB_NUMBER:
     return { ...state, counter: state.counter - action.num  }
   default:
     return state
 }
}

在Redux-Toolkit中使用

  reducers: {
       changeBanners(state, { payload }) {
         state.banners = payload
       },
       changeRecommends(state, { payload }) {
         state.recommends = payload
       }
     },
 }

在Redux-Toolkit中使用了immerjs库中的 利用了Persistent Data Structure(持久化数据结构或一致性 数据结构)算法

  • 来进行数据结构的保存数据
  • 当数据被修改时,会返回一个对象,但是新的对象会尽可能的利用之前的数据结构而不会 对内存造成浪费;

image.png

实现connect函数

connect函数 其实是通过高阶函数+高阶组件 组合形成的一种函数

  • 实现1 :通过Provider传入app的store 从Context中获取到使用的store
  • 实现2 : 组件内部设置state的默认值 通过subscribe来进行监听store中state如果变化 就使用setState修改组件内部state(内部会进行比较变化的key)然后更新组件
  • 实现3 :映射传入的props和dispach 返回给组件props
StoreContext.js页面
import { createContext } from "react";

export const StoreContext = createContext()



// connect的参数:
// 参数一: 函数
// 参数二: 函数
// 返回值: 函数 => 高阶组件

import { PureComponent } from "react";
import { StoreContext } from "./StoreContext";
// import store from "../store"

export function connect(mapStateToProps, mapDispatchToProps, store) {
  // 高阶组件: 函数
  return function(WrapperComponent) {
    class NewComponent extends PureComponent {
      constructor(props, context) {
        super(props)
        
        this.state = mapStateToProps(context.getState())
      }

      componentDidMount() {
        this.unsubscribe = this.context.subscribe(() => {
          // this.forceUpdate()
          this.setState(mapStateToProps(this.context.getState()))
        })
      }

      componentWillUnmount() {
        this.unsubscribe()
      }

      render() {
        const stateObj = mapStateToProps(this.context.getState())
        const dispatchObj = mapDispatchToProps(this.context.dispatch)
        return <WrapperComponent {...this.props} {...stateObj} {...dispatchObj}/>
      }
    }

    NewComponent.contextType = StoreContext

    return NewComponent
  }
}

实现中间件

  1. 实现一个log中间件 :每一次dispatch都需要打印对应信息
    • 利用的是monkey patch候补丁 修改store中的dispatch的方法指向
function log(store) {
  const next = store.dispatch

  function logAndDispatch(action) {
    console.log("当前派发的action:", action)
    // 真正派发的代码: 使用之前的dispatch进行派发
    next(action)
    console.log("派发之后的结果:", store.getState())
  }

  // monkey patch: 猴补丁 => 篡改现有的代码, 对整体的执行逻辑进行修改
  store.dispatch = logAndDispatch
}
log(store)
  1. 实现thunk中间件 ( 不仅仅dispatch对象 也可以处理函数)
    • 根据判断action类型 如果是 function 则需要调用该函数 否则直接dispatch
function thunk(store) {
  const next = store.dispatch
  function dispatchThunk(action) {
    if (typeof action === "function") {
      action(store.dispatch, store.getState)
    } else {
      next(action)
    }
  }
  store.dispatch = dispatchThunk
}

export default thunk

  1. 整合中间件 applyMiddleware文件 让他依次执行中间件
function applyMiddleware(store, ...fns) {
  fns.forEach(fn => {
    fn(store)
  })
}

export default applyMiddleware



const store = createStore(reducer)
applyMiddleware(store, log, thunk)

image.png