React 19 入门教程
useReducer
本教程共 50 篇 · 第 22 篇 · 更新于 2026-07-29 · 约 7 分钟阅读
ReactuseReducerreducerdispatch状态逻辑
22. useReducer
本节目标:掌握 useReducer 管理复杂状态逻辑,理解 reducer 纯函数,知道什么时候该从 useState 切换到 useReducer。
什么时候该用 useReducer
useState 简单场景够用。但状态逻辑复杂时,代码会变成这样:
function TaskList() {
const [tasks, setTasks] = useState([]);
const [editingId, setEditingId] = useState(null);
const [filter, setFilter] = useState('all');
function addTask(text) {
setTasks([...tasks, { id: Date.now(), text, done: false }]);
}
function deleteTask(id) {
setTasks(tasks.filter(t => t.id !== id));
}
function toggleTask(id) {
setTasks(tasks.map(t =>
t.id === id ? { ...t, done: !t.done } : t
));
}
// 更多 setState...
}
多个 setState 散落在各处,逻辑不好追踪。useReducer 把它们集中管理。
useReducer 的基本结构
import { useReducer } from 'react';
function TaskList() {
const [tasks, dispatch] = useReducer(tasksReducer, []);
function addTask(text) {
dispatch({ type: 'added', text });
}
function deleteTask(id) {
dispatch({ type: 'deleted', id });
}
return (/* ... */);
}
三个要素:
reducer:纯函数(state, action) => newStatedispatch:发送 action 的函数action:描述”发生了什么”的对象
写一个 reducer
reducer 是个纯函数,接收当前状态和 action,返回新状态:
function tasksReducer(tasks, action) {
switch (action.type) {
case 'added':
return [...tasks, {
id: Date.now(),
text: action.text,
done: false
}];
case 'deleted':
return tasks.filter(t => t.id !== action.id);
case 'toggled':
return tasks.map(t =>
t.id === action.id ? { ...t, done: !t.done } : t
);
case 'edited':
return tasks.map(t =>
t.id === action.id ? { ...t, text: action.text } : t
);
default:
throw Error('未知操作:' + action.type);
}
}
reducer 必须是纯函数
不修改参数,不调用 API,不做随机数。同样的输入永远返回同样的输出。
完整的待办列表
import { useReducer, useState } from 'react';
function TaskList() {
const [tasks, dispatch] = useReducer(tasksReducer, []);
const [text, setText] = useState('');
function handleAdd() {
if (!text.trim()) return;
dispatch({ type: 'added', text });
setText('');
}
return (
<div>
<input value={text} onChange={e => setText(e.target.value)} />
<button onClick={handleAdd}>添加</button>
<ul>
{tasks.map(task => (
<li key={task.id}>
<input
type="checkbox"
checked={task.done}
onChange={() => dispatch({ type: 'toggled', id: task.id })}
/>
{task.text}
<button onClick={() => dispatch({ type: 'deleted', id: task.id })}>
删除
</button>
</li>
))}
</ul>
</div>
);
}
useState vs useReducer
| useState | useReducer | |
|---|---|---|
| 适用场景 | 简单状态、独立更新 | 复杂逻辑、多个子状态 |
| 更新方式 | 直接设值 | dispatch action |
| 逻辑位置 | 散落在事件处理 | 集中在 reducer |
| 可预测性 | 一般 | 高(所有转换都在 reducer 里) |
| 测试 | 需要渲染组件 | 直接测 reducer 函数 |
什么时候切换
一个组件里有三个以上相关的 setState,或者 setState 逻辑超过 5 行,考虑 useReducer。
用 Immer 配合 useReducer
reducer 里也要不可变更新,嵌套深了同样痛苦。用 useImmerReducer:
import { useImmerReducer } from 'use-immer';
function tasksReducer(tasks, action) {
switch (action.type) {
case 'added':
tasks.push({ id: Date.now(), text: action.text, done: false });
return;
case 'toggled':
const task = tasks.find(t => t.id === action.id);
task.done = !task.done;
return;
}
}
看起来像直接修改,Immer 在背后生成不可变结果。
action 的命名习惯
// 推荐:动词 + 名词
dispatch({ type: 'task_added' });
dispatch({ type: 'task_deleted' });
dispatch({ type: 'user_login' });
dispatch({ type: 'form_reset' });
// 也可以按业务含义
dispatch({ type: 'increment' });
dispatch({ type: 'decrement' });
action 对象没有固定格式,但 { type: 'xxx' } 是约定俗成的。
本节要点
- 复杂状态逻辑用 useReducer 集中管理
- reducer 是纯函数
(state, action) => newState - dispatch action 描述”发生了什么”
- 简单场景用 useState,复杂场景用 useReducer