【源码共读】| 简易实现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来保证数据的不可变性。

相关推荐
weixin_4316004410 小时前
NestJS 入门(7):生命周期钩子——构造函数和 `OnModuleInit` 差在哪?
前端·后端·学习·node.js·nest.js
cindershade11 小时前
用原生 HTML5 视频与 Canvas 实现浏览器端视频抽帧封面
前端
笨鸟先飞,勤能补拙11 小时前
Web 服务器与计算机网络全景原理
运维·服务器·前端·网络·人工智能·计算机网络·web安全
NutShell Wang11 小时前
Wails v3 Beta 实战:用显式对象模型重写你的第一个 Go 桌面应用
前端·人工智能·go·vibe coding
cindershade11 小时前
前端国际化工程实践:语言包拆分、动态加载与日期数字格式统一
前端
kisbad11 小时前
Day 036|OpenAI Agents SDK 快速开始:今天跑通第一个 Agent
java·前端·javascript
qq_2676128911 小时前
Gitee CodePecker行业场景化落地指南:金融、车联网与IoT双引擎选型与工程化路径
前端·gitee·自动化
初晨未凉11 小时前
elementui自定义内容图片预览
前端
无糖可可果13 小时前
从零读懂一个 Next.js 全栈笔记应用
前端
Maxkim13 小时前
DeepSeek Harness 源码深度分析:像 VS Code 一样插件化的 Agent 框架
前端·架构