迁移状态逻辑至 Reducer:dispatch 与 useReducer 重构
随着你的组件功能越来越丰富,组件里的状态更新逻辑也会变得越来越庞大繁琐。例如,在一个任务管理应用中,你可能需要处理“添加任务”、“编辑任务文本”、“勾选已完成”、“删除任务”四五种操作。
如果把这四五种操作的数组过滤、展开复制全部塞在不同的事件处理函数中,组件的代码会变得异常臃肿,而且很难一眼看清这个组件到底支持哪些业务行为。
为了解决这个问题,React 提供了 Reducer 机制:把所有的状态计算逻辑,从组件函数中全部抽离出来,集中存放在一个干净纯粹的单一函数中。
1. 迁移到 Reducer 的标准三步法
第一步:将“设置状态”改为“分发 Action”
以往我们是直接命令计算机怎么改数据:
function handleDeleteTask(taskId) {
setTasks(tasks.filter(t => t.id !== taskId));
}
使用 Reducer 时,你不再告诉 React“怎么更新数组”,而是告诉 React“用户刚刚做了什么操作”。这种描述用户行为的对象,被称为 Action。
通过调用 dispatch(分发) 函数来派发这个 Action:
function handleDeleteTask(taskId) {
dispatch({
type: 'deleted', // 动作类型(通常用过去分词或动词)
id: taskId // 附带的操作数据
});
}
第二步:编写一个纯函数 Reducer(Reducer Function)
Reducer 是一个普通的 JavaScript 函数,它接收两个参数:当前的状态(state) 以及 刚刚分发的动作(action),并计算出下一个全新的状态并返回:
function tasksReducer(tasks, action) {
switch (action.type) {
case 'added': {
return [
...tasks,
{
id: action.id,
text: action.text,
done: false
}
];
}
case 'changed': {
return tasks.map(t => {
if (t.id === action.task.id) {
return action.task;
} else {
return t;
}
});
}
case 'deleted': {
return tasks.filter(t => t.id !== action.id);
}
default: {
throw Error('未知的 action 类型: ' + action.type);
}
}
}
Reducer 铁律:Reducer 函数必须是绝对纯净的纯函数! 它不能包含异步请求、不能包含定时器、不能直接变异原状态。它只负责根据当前状态和动作,计算并返回新状态。
第三步:在组件中使用 useReducer Hook
从 React 中引入 useReducer:
import { useReducer } from 'react';
把原本的 useState 替换为 useReducer:
// 之前:
// const [tasks, setTasks] = useState(initialTasks);
// 现在:传入你的 reducer 函数以及初始状态
const [tasks, dispatch] = useReducer(tasksReducer, initialTasks);
现在,你的组件内部代码变得极其清爽:所有的按钮点击,都只需要一行短短的 dispatch({ type: '...' })!所有复杂的数组变更逻辑全都被移到了组件外部的 tasksReducer 中,你甚至可以把这个 reducer 提取到单独的测试文件中进行纯单元测试!
2. useState vs useReducer:如何选择?
| 权衡维度 | useState | useReducer |
|---|---|---|
| 代码体积 | 初始代码很少,适合几个简单独立的值 | 需要提前写 reducer 函数与 action,初始模版稍多 |
| 可读性 | 状态逻辑简单时极具可读性 | 当状态变更逻辑复杂且分支众多时,可读性极佳 |
| 调试体验 | 散落的 setter 较难统一步骤排查 | 可以在 reducer 里打印日志,每一步 action 清晰可循 |
| 可测试性 | 逻辑与组件紧密耦合,较难单独测试 | Reducer 是纯函数,可以独立导入进行单元测试 |
经验法则:如果你发现一个组件里经常有几个状态需要同时更新,或者单个状态的修改方式超过了 3 种,果断重构为 useReducer!
总结
- Reducer 将组件内部杂乱的分散状态更新逻辑,统一收拢为单一纯函数;
- 不再就地直接修改状态,而是分发表达意图的
action对象(dispatch({ type: '...' })); - Reducer 函数接收
(state, action)并返回计算出的新 state; useReducer让大型组件的数据流向变得无比清晰规整。