学习 Redux Toolkit :从 Context 误区到 createSlice 实践

本文说明:本文是基于 Redux Toolkit 官方文档及其 maintainer 发布的博文做的整理,双语对照以防止与原文有歧义。文末有完整的原文链接可供详细学习。

希望这份整理对你有帮助。

一、开篇:Context 不是状态管理系统

"Should I use Context or should I use Redux?"

"我应该用上下文还是用 Redux?"

And they seem to think that Context itself is a state management system. It's not .

他们似乎认为 Context 本身就是一个状态管理系统。 其实不是

It's a dependency injection mechanism, and you can put whatever value you want in Context, and most often you are the one managing that state in a React component, with the useState hook or the useReducer hook. And you're the one deciding where the state lives, handling how to update it, and then putting the value into Context for distribution.

它是一种依赖注入机制,你可以在上下文中输入任何你想要的值,通常你会在 React 组件中管理该状态,使用 useState 钩子或 useReducer 钩子。你负责决定状态的所在位置,处理如何更新,然后把这个值放进 Context 进行分发。

So yeah, useReducer plus useContext together kind of make up a state management system. And that one is more equivalent to what Redux does with React, but Context by itself is not a state management system.

所以,是的,useReducer 加上 useContext 一起构成了一个状态管理系统。这个更类似于 Redux 对 React 的处理,但 Context 本身并不是一个状态管理系统 。

既然 Context 本身不是状态管理方案,那么 Redux Toolkit 提供了怎样的替代方案?我们先从它的 API 全景看起。

二、Redux Toolkit 工具箱里有什么?

Redux Toolkit 包含以下 API

  • configureStore(): 包装 createStore 以提供简化的配置选项和良好的默认值。它可以自动组合您的切片 reducer,添加您提供的任何 Redux 中间件,默认情况下包含 redux-thunk,并启用 Redux DevTools Extension 的使用。
  • createReducer(): 允许您提供操作类型到 case reducer 函数的查找表,而不是编写 switch 语句。此外,它会自动使用 immer,让您可以使用正常的可变代码编写更简单的不可变更新,例如 state.todos[3].completed = true
  • createAction(): 为给定的操作类型字符串生成一个操作创建器函数。
  • createSlice(): 接受 reducer 函数对象、切片名称和初始状态值,并自动生成具有相应操作创建器和操作类型的切片 reducer。
  • combineSlices(): 将多个切片组合成一个 reducer,并允许在初始化后"延迟加载"切片。
  • createAsyncThunk: 接受一个动作类型字符串和一个返回 Promise 的函数,并生成一个 thunk,根据该 Promise 分发 pending/fulfilled/rejected 动作类型。
  • createEntityAdapter: 生成一组可重用的 reducer 和 selector,用于管理存储中的规范化数据。
  • 来自 Reselect 库的 createSelector 实用程序,为了方便使用而重新导出。

注意到上面多次提到 Immer 了吗?它正是 RTK 让你能"直接修改 state"的秘密武器。

三、Immer:为什么你能直接"修改"state?

Immer(德语为:always)是一个小型包,可让您以更方便的方式使用不可变状态。

Immer 简化了不可变数据结构的处理

Immer 可以在需要使用不可变数据结构的任何上下文中使用。例如与 React state、React 或 Redux reducers 或者 configuration management 结合使用。不可变的数据结构允许(高效)的变化检测:如果对对象的引用没有改变,那么对象本身也没有改变。此外,它使克隆对象相对便宜:数据树的未更改部分不需要复制,并且在内存中与相同状态的旧版本共享

一般来说,这些好处可以通过确保您永远不会更改对象、数组或映射的任何属性来实现,而是始终创建一个更改后的副本。在实践中,这可能会导致代码编写起来非常麻烦,并且很容易意外违反这些约束。 Immer 将通过解决以下痛点来帮助您遵循不可变数据范式:

  1. Immer 将检测到意外 mutations 并抛出错误。
  2. Immer 将不再需要创建对不可变对象进行深度更新时所需的典型样板代码:如果没有 Immer,则需要在每个级别手动制作对象副本。通常通过使用大量 ... 展开操作。使用 Immer 时,会对 draft 对象进行更改,该对象会记录更改并负责创建必要的副本,而不会影响原始对象。
  3. 使用 Immer 时,您无需学习专用 API 或数据结构即可从范例中受益。使用 Immer,您将使用纯 JavaScript 数据结构,并使用众所周知的安全地可变 JavaScript API。

代码对比:不使用 Immer vs 使用 Immer

不使用 Immer

如果没有 Immer,我们将不得不小心地浅拷贝每层受我们更改影响的 state 结构

javascript 复制代码
const nextState = baseState.slice() // 浅拷贝数组
nextState[1] = {
    // 替换第一层元素
    ...nextState[1], // 浅拷贝第一层元素
    done: true // 期望的更新
}
// 因为 nextState 是新拷贝的, 所以使用 push 方法是安全的,
// 但是在未来的任意时间做相同的事情会违反不变性原则并且导致 bug!
nextState.push({title: "Tweet about it"})

使用 Immer

使用 Immer,这个过程更加简单。我们可以利用 produce 函数,它将我们要更改的 state 作为第一个参数,对于第二个参数,我们传递一个名为 recipe 的函数,该函数传递一个 draft 参数,我们可以对其应用直接的 mutations。一旦 recipe 执行完成,这些 mutations 被记录并用于产生下一个状态。 produce 将负责所有必要的复制,并通过冻结数据来防止未来的意外修改。

javascript 复制代码
import {produce} from "immer"

const nextState = produce(baseState, draft => {
    draft[1].done = true
    draft.push({title: "Tweet about it"})
})

Immer 核心要点总结

简单说应该就是state是不可以变的,但immer提供了state这种不可变数据的更改?

  1. 最终结果仍遵循不可变原则
    • 原始 state 不会被修改
    • 修改后产生一个全新的对象
    • 未变化的部分共享引用(结构共享)
  2. 但编写体验是"可变"的
    • 你直接对 draft 赋值:draft[1].done = true
    • 你直接 push:draft.push(...)
    • 看起来就像修改了原对象

Immer 工作原理图示

基本思想是,使用 Immer,您会将所有更改应用到临时 draft ,它是 currentState 的代理。一旦你完成了所有的 mutations ,Immer 将根据对 draft statemutations 生成 nextState。这意味着您可以通过简单地修改数据来与数据交互,同时保留不可变数据的所有好处。

理解了 Immer 的原理,我们来看 RTK 中最核心的抽象------Slice(切片)。

四、Slice:Redux 开发的模块化核心

一、什么是 Slice?

在 Redux Toolkit 中,Slice(切片) 是核心概念之一,用于简化 Redux 开发流程。

核心特性

  • 自动生成 Action:无需手动定义 ACTION_TYPE 常量和 action creator 函数

  • 不可变更新:内部集成 Immer 库,允许直接"修改"状态,同时保证生成新的不可变对象

  • 模块化管理:将应用状态拆分为独立模块(用户模块、酒店模块、审核模块等)

    本 Slice 专门负责管理酒店审核模块的状态逻辑,包括列表数据、筛选条件、加载状态及审核操作结果。

Slice 处理的是同步更新,那异步请求呢?RTK 专门提供了 createAsyncThunk。

五、createAsyncThunk:异步请求的标准方案

概述

一个接受 Redux action 类型字符串和回调函数的函数,该回调函数应返回一个 promise。它根据您传入的动作类型前缀生成 promise 生命周期动作类型,并返回一个 thunk 动作创建者,该创建者将运行 promise 回调并根据返回的 promise 分派生命周期动作。

本节概述了处理异步请求生命周期的标准推荐方法。

它不会生成任何 reducer 函数,因为它不知道您要获取什么数据、如何跟踪加载状态,以及如何处理返回的数据。您应该编写自己的 reducer 逻辑来处理这些操作,并使用适合您应用程序的加载状态和处理逻辑。

掌握了这些核心概念,你已经可以开始使用 RTK 了。更多细节可以参考以下资源。

六、延伸学习

createAsyncThunk | Redux Toolkit 中文

createSlice | Redux Toolkit 中文

什么时候(以及什么时候不该)入手 Redux --- When (and when not) to reach for Redux

相关推荐
CodeCxil2 小时前
基于Vue的在线Online Word文档编辑器-效果预览
前端·vue.js·word
lhbian2 小时前
30分钟搭建PHP+Java全栈Web应用
java·前端·php
有谁看见我的剑了?2 小时前
Linux 内存巨页与透明巨页学习
java·linux·学习
red_redemption2 小时前
自由学习记录(166)
学习
SuperEugene2 小时前
Vue3 配置驱动表格:列配置/操作配置/分页配置,统一表格渲染|配置驱动开发实战篇
前端·javascript·vue.js·驱动开发·架构
weixin_471383032 小时前
[特殊字符] React Flow 从入门到理解
开发语言·前端·javascript
CompaqCV2 小时前
OpencvSharp 算子学习教案之 - Cv2.Multiply
学习·c#·opencvsharp算子·opencv教程
三翼鸟数字化技术团队2 小时前
前端水印实现方案
前端
CompaqCV2 小时前
OpencvSharp 算子学习教案之 - Cv2.Subtract 重载2
学习·c#·opencvsharp算子·opencv教程