运用reduxjs/toolkit和实现

113 阅读3分钟

资源:

  1. redux/toolkit 英文文档
  2. redux/toolikt中文文档
  3. redux-toolkit源码
  4. redux最佳实践
  5. immer文档

目标

  1. 掌握rtk用法
  2. 实现rtk内的configureStore, createSlice, createReducer

用法

  • store写法
import { configurStore } from '@reduxjs/toolkit'
import counterReducer from './counterReducer'

const store = configureStore({
    reducer: {
        //counter是reducer的name
        counter: counterReducer
    }
})

export default store
  • counterReducer写法, 有点像dva的models
import { createSlice } from '@reduxjs/toolkit'

const counterReducer = createSlice({
    name: 'counter',
    initialState: { value: 0 },
    reducers: {
        increment: (state) => {
            state.value += 1
        }
    }
})

export const { increment } = counterReducer.actions

export default counterReducer.reducer
  • UI组件写法
import React, { useLayoutEffect, useReducer } from 'react'
import store from './../store'
import { increment } from '../store/counterReducer'

export default function RtkPage() {

    const count = store.getState().counter.value

    const [, forceUpdate] = useReducer(x => x+1, 0)

    useLayoutEffect(() => {
        const unsubscribe = store.subscribe(() => {
            forceUpdate()
        })

        return () => {
            unsubscribe()
        }
    }, [])

    return (
        <div>
            <button onClick={() => store.dispatch({type: 'counter/increment'})}>{count}</button>
            <button onClick={() => store.dispatch(increment())}>{count}</button>
        </div>
    )
}
  • 首先实现configureStore
  • 思考下configureStore 干了哪些事情,返回了什么
    1. 创建store 2.返回store
import { createStore } from 'redux'
function configureStore({reducer}){
    const rootReducers = combineReducers(reducer)
    const store = createStore(rootReducers)
    
    return store
}

export {configureStore}
  • 实现createSlice
  1. 入参是什么, 返回了什么 入参为 name, initialState, reducers, 返回name, acitons, reducer
  2. 我们需要的actions 是什么样的 {type: 'counter/increment', payload: payload} / (incement())
  3. type需要我们自己去拼接, 那么拼接需要用到的都是什么, 第一个reducer的name, 第二个 reducer的键名 所以第一步我们需要获取reducer的键名数组, 第二步需要通过这个type去返回需要的type,payload
  4. reducer需要返回redux需要的reducer函数,所以需要createReducer帮助我们去创建一个reducer
function actionCreator(type) {
    function creatorActions(...args) {
        return {
            type,
            payload: args[0]
        }
    }
    
    return creatorActions
}

function createSplice({ name, initialState, reducers }){
    // 获取所有用户输入的reducer键名
    const reducerNames = Object.keys(reducers)
    
    let actionsMap = {}
    
    let sliceCaseReducersByType = {}
    
    reducerNames.forEach(reducerName => {
        // 获取每个用户输入的reducer
        const r = reducers(reducerName)
        // 拼接成需要的type格式
        const type = `${name}/${reducerName}`
        // 使用拼接好的type当做reducer的键名, 键值就是用户输入的内容
        sliceCaseReducersByType[type] = r
        // 返回正确的action对象 {type: type, payload: payload}
        actionsMap[reducerName] = actionCreator(type)
    })
    
   function buildReducer(){
       // 通过immer提供的api去创建一个reducer 
       // 注意: immer10以上没有这个api 可以使用@reduxjs/toolkit下的这个api
       return createReducer(initialState,(builder) => {
           for(let key in sliceCaseReducersByType) {
            builder.addCase(key, sliceCaseReducersByType[key])
           }
       })
   }
    
   let _reducer
    
   return {
       name, 
       actions: actionsMap,
       reducer: (state, action) => {
           if(!_reducer) _reducer = buildReducer()
           return _reducer(state,action)
       }
   }
}
  • 下面我们实现createReducer这个api
  • 思考下:
  1. 使用这个api的时候我们传递了默认值和一个callback并且返回一个新的reducer
  2. 那我在这个api中要通过这个callback函数进行创建reducer
  3. 先去实现callback这个回调函数, 这个回调函数会创建一个addCase的方法,并且返回拼接后的type为键名,用户输入的reducer为键值的一个对象
function createReducer(initialState, mapOrBuilderCallback){
    let [actionsMap] = executeReducerBuilderCallback(mapOrBuilderCallback)
    
    function reducer(state = initialState, action) {
        // 拿到reducer内的case
        let caseReducers = [actionsMap[action.type]]
        // 通过reduce方法
        return caseReducers.reduce((previousState, caseReucer) => {
            if(caseReducer) {
                // 通过immer10 以下的api创建reducer并且传递入参
                return createNextState(previousState, (draft) => {
                    return caseReucer(draft, action)
                })
            }
            // 如果caseReducer没值直接返回previousState
            return previousState
        }, state)
        
    }
    
    return reducer
}

function executeReducerBuilderCallback(mapOrBuilderCallback) {
    let actionsMap = {}
    // 我们使用api的时候有一个addCase的方法,所以我们也去创建一个
    // 接收的是当前拼接好的键名和用户输入的reducer
    const builder = {
        addCase: (type, reducer) => {
            actionsMap[type] = reducer
            return builder
        }
    }
    // 把builder当做回调函数传递过去
    mapOrBuilderCallback(builder)
    
    return [actionsMap]
}