【源码共读】| 简易实现redux-toolkit

我们在使用 Redux 时可能会遇到了以下几个问题:

● 配置 Store 复杂。

● 需要添加许多包来实现异步、日志等功能。

● 基于 Redux 的灵活性,容易导致千差万别的编写方式。

为了解决这些问题,官方推出了一套类似脚手架的工具,以规范化 Redux 的使用。那么,如果让我们来实现一个rtk,应该怎么做呢?

简单使用

以官方文档为例,让我们先看一下用法。
redux-toolkit.js.org/tutorials/q...

官网上有详细的教程,这里不再赘述,总结如下几点:

  • 创建rtkStore
  • 使用React-Redux的Provider传递store
  • 创建React State Slice
  • 在组件中调用

简单实现

那么这个过程是如何实现的呢?以计数器为例,首先要实现的是configureStore

configureStore

对应源码:github.com/reduxjs/red... target:

  • 传入一个或多个reducer
  • 构建出redux store
tsx 复制代码
// 计数器的例子
export default configureStore({
  reducer: {
    counter: countReducer
  }
})
tsx 复制代码
import { combineReducers, createStore } from 'redux'
export function configureStore({ reducer }) {
  const rootReducer = combineReducers(reducer)

  const store = createStore(rootReducer)

  return store
}

我们只需将利用combineReducersreducers组合到一起,然后创建store

当然,在源码中传入中间件并进行增强。由于这只是一个简易实现,所以只完成了核心功能的部分。

createSlice

对应源码:github.com/reduxjs/red... target:

  • 传入name, initialState, reducers
  • 返回一个处理好的对象
javascript 复制代码
{
    name,
    actions: actionCreators,
    reducer: (state, action) => {
      if (!_reducer) _reducer = buildReducer()

      return _reducer(state, action)
    }
  }

处理actions
tsx 复制代码
import createAction from './createAction'
import createReducer from './createReducer'

export default function createSlice({ name, initialState, reducers }) {
  const reducerNames = Object.keys(reducers)

  // 保存action创建函数
  const actionCreators = {}

  reducerNames.forEach((reducerName) => {
    const type = `${name}/${reducerName}`
    // 创建对应的action
    // sample: { type: 'counter/increment', payload: 1}
    actionCreators[reducerName] = createAction(type)
  })


  return {
    // 返回一个对象,包含name、actions和reducer属性
    name,
    actions: actionCreators,
    
  }
}
tsx 复制代码
// createAction
export default function createAction(type) {
  function actionCreator(...args) {
    return { type, payload: args[0] }
  }

  actionCreator.type = type
  return actionCreator
}

处理reducer

上面我们处理了action,接下来处理reducer部分

tsx 复制代码
import createAction from './createAction'
import createReducer from './createReducer'

export default function createSlice({ name, initialState, reducers }) {
  // 省略...
  // 保存reducer
  const sliceCaseReducersByType = {}

  reducerNames.forEach((reducerName) => {
    // 取出reducers中的每个reducer函数
    const maybeReducerWithPrepare = reducers[reducerName]
    const type = `${name}/${reducerName}`
    sliceCaseReducersByType[type] = maybeReducerWithPrepare
    actionCreators[reducerName] = createAction(type)
  })

  function buildReducer() {
    return createReducer(initialState, (builder) => {
      for (let key in sliceCaseReducersByType) {
        // @key:counter/incremen
        // @sliceCaseReducersByType[key]:state => {
        //   state.value += 1;
        // }
        builder.addCase(key, sliceCaseReducersByType[key])
      }
    })
  }

  let _reducer

  return {
    // 返回一个对象,包含name、actions和reducer属性
    name,
    actions: actionCreators,
    reducer: (state, action) => {
      if (!_reducer) _reducer = buildReducer()

      // 调用_reducer函数处理state和action,并返回处理后的state
      return _reducer(state, action)
    }
  }
}

executeReducerBuilderCallback的作用

  • 返回标准的builder对象
tsx 复制代码
// createReducer
import createNextState from 'immer'

export default function createReducer(initialState, mapOrBuilderCallback) {
  // 获取存储action和reducer的对象
  let [actionMap] = executeReducerBuilderCallback(mapOrBuilderCallback)

  function reducer(state = initialState, action) {
    // 获取action对应的reducer数组
    const caseReducers = [actionMap[action.type]]
    // 依次执行reducer
    // 利用immer库,返回一个新的state
    return caseReducers.reduce((previousState, caseReducer) => {
      if (caseReducer) {
        return createNextState(previousState, (draft) => {
          return caseReducer(draft, action)
        })
      }
      return previousState
    }, state)
  }

  return reducer
}

function executeReducerBuilderCallback(mapOrBuilderCallback) {
  const actionMap = {}

  const builder = {
    addCase: (type, reducer) => {
      actionMap[type] = reducer
      return builder
    }
  }
  // 传入一个标准的builder对象,该对象包含addCase方法
  mapOrBuilderCallback(builder)

  // 使用数组的目的是为了方便解构自定义变量名字
  return [actionMap]
}
tsx 复制代码
// createSlice
import createAction from './createAction'
import createReducer from './createReducer'

export default function createSlice({ name, initialState, reducers }) {
  const reducerNames = Object.keys(reducers)

  // 保存action创建函数
  const actionCreators = {}
  // 保存reducer
  const sliceCaseReducersByType = {}

  reducerNames.forEach((reducerName) => {
    const maybeReducerWithPrepare = reducers[reducerName]
    const type = `${name}/${reducerName}`
    // 将传入的reducer函数赋值给sliceCaseReducersByType对象中的type属性
    sliceCaseReducersByType[type] = maybeReducerWithPrepare
    // sample: { type: 'counter/increment', payload: 1}
    actionCreators[reducerName] = createAction(type)
  })

  function buildReducer() {
    return createReducer(initialState, (builder) => {
      for (let key in sliceCaseReducersByType) {
        builder.addCase(key, sliceCaseReducersByType[key])
      }
    })
  }

  let _reducer

  return {
    name,
    actions: actionCreators,
    reducer: (state, action) => {
      if (!_reducer) _reducer = buildReducer()

      // 返回处理后的state
      return _reducer(state, action)
    }
  }
}

至此,我们已经完成了rtk中的核心功能

  • 规范化action
  • 处理更新后的state

总结

通过实现简易的rtk,我们可以了解到rtk的实现原理是通过传入的namereducerName来规范化action,并在数据更新时,使用了immer来保证数据的不可变性。

相关推荐
_.Switch36 分钟前
Python Web 应用中的 API 网关集成与优化
开发语言·前端·后端·python·架构·log4j
一路向前的月光40 分钟前
Vue2中的监听和计算属性的区别
前端·javascript·vue.js
长路 ㅤ   41 分钟前
vite学习教程06、vite.config.js配置
前端·vite配置·端口设置·本地开发
长路 ㅤ   41 分钟前
vue-live2d看板娘集成方案设计使用教程
前端·javascript·vue.js·live2d
Fan_web1 小时前
jQuery——事件委托
开发语言·前端·javascript·css·jquery
安冬的码畜日常1 小时前
【CSS in Depth 2 精译_044】第七章 响应式设计概述
前端·css·css3·html5·响应式设计·响应式
莹雨潇潇2 小时前
Docker 快速入门(Ubuntu版)
java·前端·docker·容器
Jiaberrr2 小时前
Element UI教程:如何将Radio单选框的圆框改为方框
前端·javascript·vue.js·ui·elementui
Tiffany_Ho3 小时前
【TypeScript】知识点梳理(三)
前端·typescript
安冬的码畜日常4 小时前
【D3.js in Action 3 精译_029】3.5 给 D3 条形图加注图表标签(上)
开发语言·前端·javascript·信息可视化·数据可视化·d3.js