揭秘 Redux Toolkit:用原生 JavaScript 一窥内部
Source: Dev.to
请提供您希望翻译的完整文本内容,我将按照要求将其翻译为简体中文,并保留原始的格式、Markdown 语法以及技术术语。谢谢!
实践探索 Redux Toolkit 如何在基于核心 JavaScript 和 Redux 原则的基础上简化状态管理
介绍
嗨!如果你曾在真实项目中与 Redux 纠缠过,你一定知道它常常像一只充斥着样板代码的怪兽——动作四处飞舞,reducer 像杂草一样蔓延。这时 Redux Toolkit 就像友好的副手出现,削减冗余,让你专注于真正重要的事:构建功能。
在本指南中,我们将揭开 Redux Toolkit 实际内部工作原理的面纱,一切都基于原生 JavaScript 和 Redux 基础。你将了解到它并非魔法,而是聪明的抽象,使你的代码更简洁、更易维护。阅读完本指南后,你将能够:
- 识别驱动它的 JavaScript 模式
- 自信地排查问题
- 在需要时自行实现简化
无论你是 Redux 新手还是经验丰富的老手,这都将提升你的思维模型。让我们开始吧!
目录
基础:Redux Toolkit 是什么(以及不是什麼)
先从简单的说起。Redux Toolkit 并不是对 Redux 的完整重写;它是 基于 Redux 构建的一套实用工具,旨在减少样板代码并强制最佳实践。其核心全部是 JavaScript:函数、对象以及不可变更新。
为什么这很重要?
在原生 Redux 中,你需要手动:
- 创建 action‑type 字符串
- 编写 action‑creator 函数
- 用
switch语句构建 reducer
这种做法容易出错且冗长。Redux Toolkit 将这些步骤封装在更高级的 API(如 createSlice)中,底层为你生成样板代码。
createSlice 的作用
createSlice 接收一个包含 name、initialState 和 reducers 的对象。它返回一个 slice 对象,其中包含:
- Action 创建器(
slice.actions) - reducer 函数(
slice.reducer)
在内部它会:
- 生成类似
${sliceName}/${reducerName}的 action 类型 - 创建返回
{ type, payload }对象的 action‑creator 函数 - 构建一个 reducer,将这些类型映射到提供的 reducer 函数,并使用 Immer 允许“变异”语法,同时保持更新的不可变性。
下面是一个 概念性(简化版)createSlice 的实现——并非实际源码:
function createSlice({ name, initialState, reducers }) {
const actions = {};
const reducerCases = {};
for (const [reducerName, reducerFn] of Object.entries(reducers)) {
const type = `${name}/${reducerName}`;
actions[reducerName] = (payload) => ({ type, payload });
reducerCases[type] = reducerFn;
}
const reducer = (state = initialState, action) => {
const handler = reducerCases[action.type];
return handler ? handler(state, action) : state;
};
return { reducer, actions };
}
提示: 初学者常犯的一个错误是认为 Redux Toolkit “会直接变更”状态。事实并非如此——Immer 会拦截看似可变的代码并生成不可变的更新。
始终记住: Redux Toolkit 强制执行 Redux 的不可变性规则,以防止 bug。
Source: …
实践示例:构建一个简单的 Slice
好了,你已经掌握了理论——现在来实际操作一下。假设你在构建一个 todo 应用。在原生 Redux 中,你需要为 action 类型、action 创建函数和 reducer 分别创建文件。而使用 Toolkit,只需一次 createSlice 调用即可。
安装
npm install @reduxjs/toolkit
(假设你在一个 React 项目中。)
定义 slice
import { createSlice } from '@reduxjs/toolkit';
const todosSlice = createSlice({
name: 'todos',
initialState: [],
reducers: {
addTodo: (state, action) => {
// 看起来是可变的,但 Immer 会处理它!
state.push(action.payload);
},
toggleTodo: (state, action) => {
const todo = state.find(t => t.id === action.payload);
if (todo) {
todo.completed = !todo.completed;
}
},
},
});
export const { addTodo, toggleTodo } = todosSlice.actions;
export default todosSlice.reducer;
设置 store
configureStore 是对 Redux 的 createStore 的轻量封装。它会添加诸如 Redux Thunk 中间件和 DevTools 集成等实用默认配置。
import { configureStore } from '@reduxjs/toolkit';
import todosReducer from './todosSlice';
export const store = configureStore({
reducer: {
todos: todosReducer,
},
});
在组件中使用 slice
import { useSelector, useDispatch } from 'react-redux';
import { addTodo } from './todosSlice';
function TodoList() {
const todos = useSelector(state => state.todos);
const dispatch = useDispatch();
return (
<div>
{todos.map(todo => (
<li key={todo.id}>{todo.text}</li>
))}
<button
onClick={() =>
dispatch(
addTodo({ id: Date.now(), text: 'New todo', completed: false })
)
}
>
Add Todo
</button>
</div>
);
}
当 addTodo 被 dispatch 时,Redux Toolkit 会生成如下的 action:
{ "type": "todos/addTodo", "payload": { "id": 123, "text": "New todo" } }
得益于 Immer,reducer 能以不可变的方式处理它。
专业提示: 始终同时导出 actions 和 reducer。忘记导出 actions 是导致 “undefined action” 错误的常见原因。
Source: …
可视化直觉:底层数据流
图片有帮助,对吧?下面是 Redux Toolkit 中单向数据流的思维模型:
Component
│
▼
dispatch(action) ──► Store
│
▼
reducers (with Immer) → new state
│
▼
store notifies subscribers
│
▼
Component re‑renders
- Dispatch(分发):
dispatch(addTodo(payload))只是一条普通的函数调用,返回一个 action 对象。 - Store(存储): Store 将该 action 通过 reducer 链进行处理。
- Reducers(简化器): Toolkit 的 reducer 接收一个 draft 状态(由 Immer 提供),你可以对其“变更”。Immer 记录这些更改并生成一个新的不可变状态。
- Subscribers(订阅者):
useSelector(或connect)会订阅 store;当它关注的状态片段发生变化时,组件会重新渲染。
实际案例:管理异步数据
(章节占位符 – 在此添加您的 async‑thunk 示例。)
高级技巧:自定义和扩展
(章节占位符 – 讨论 createAsyncThunk、中间件、自定义 reducers 等。)
常见错误:需避免的陷阱
- 假设 Toolkit 会直接修改状态 – 请记住,实际的工作是由 Immer 完成的。
- 忘记导出 actions – 会导致 “undefined is not a function” 错误。
- 将 Toolkit 与手动
switchreducer 混用 – 虽然可以运行,但违背了抽象的初衷。 - 在 reducer 之外修改状态 – 切勿直接修改 store 的状态;必须通过 actions 进行。
完成总结
Redux Toolkit 并非魔法;它是一套设计良好的实用工具,依赖于普通的 JavaScript 模式——对象遍历、模板字面量和函数组合——同时为你处理 Redux 中繁琐的部分。了解其内部工作原理后,你可以:
- 编写更简洁、更易维护的代码
- 自信地进行调试
- 在需要时扩展或甚至自行实现抽象层
编码愉快! 🚀
Redux Toolkit 工作原理(内部实现)
当 action creator 返回一个对象时,中间件(例如 Thunk)会检查该 action 是否为异步的。如果是,中间件会处理异步流程;否则,action 会直接传递给根 reducer,根 reducer 再委派给相应的 slice reducer。
可以把它想象成一个 JavaScript 事件总线:store 是一个提供三个核心方法的对象:
dispatch– 将 action 通过中间件链发送。subscribe– 注册监听器,在每次状态变化后运行。getState– 返回当前状态。
configureStore 用来创建这个 store,并添加调试、DevTools 集成等增强功能。
直观可视化(流程图)
- Action 被分发 → 通过 中间件链(一个函数数组,每个函数调用
next)。 - Reducer 被调用 → 使用不可变更新(
Object.assign、展开运算符或 Immer)来更新状态。 - 订阅者收到通知 → React‑Redux 的
useSelector钩子触发组件重新渲染。
简化版 JavaScript 模拟 Store 的 Dispatch 循环
function createSimpleStore(reducer, initialState) {
let state = initialState;
const listeners = [];
function dispatch(action) {
// Immutable update here
state = reducer(state, action);
listeners.forEach(listener => listener());
}
function subscribe(listener) {
listeners.push(listener);
// Return an unsubscribe function
return () => listeners.splice(listeners.indexOf(listener), 1);
}
function getState() {
return state;
}
return { dispatch, subscribe, getState };
}
提示框: 为了提升可访问性,请确保应用的状态变化不会破坏键盘导航。对依赖 Redux 状态的 toast 或 modal 使用 ARIA live 区域。这类小的用户体验改进并不会被 Redux Toolkit 自动处理。