useReducer
是 React 中用于管理复杂状态逻辑的 Hook,尤其适合处理具有多个子值的状态对象、存在复杂状态转换逻辑或多个操作影响同一状态的场景。它的工作方式类似 Redux 的 reducer 模式,通过 action 驱动状态更新,让状态变化逻辑更可预测、可维护。
核心概念
- state:当前的状态值
- action:一个描述“要做什么”的普通对象(通常包含
type
字段表示操作类型,以及其他必要数据) - reducer:一个纯函数,接收当前
state
和action
,返回新的state
((state, action) => newState
) - dispatch:用于发送 action 的函数(调用
dispatch(action)
触发 reducer 执行)
基础用法
1. 定义 reducer 函数
reducer 是纯函数,根据 action.type
决定如何更新状态,不能直接修改原状态,必须返回新状态。
// 定义 reducer:根据 action 处理状态
function todoReducer(state, action) {switch (action.type) {case 'ADD_TODO':// 返回新数组(不修改原数组)return [...state,{ id: Date.now(), text: action.text, done: false }];case 'TOGGLE_TODO':// 映射新数组,只修改目标项return state.map(todo =>todo.id === action.id ? { ...todo, done: !todo.done } : todo);case 'DELETE_TODO':// 过滤掉要删除的项return state.filter(todo => todo.id !== action.id);default:// 未知 action 时返回原状态return state;}
}
2. 在组件中使用 useReducer
import { useReducer } from 'react';function TodoApp() {// 初始化状态(空数组),获取 [状态, dispatch函数]const [todos, dispatch] = useReducer(todoReducer, []);const [inputText, setInputText] = useState('');// 处理添加任务const handleAdd = () => {if (!inputText.trim()) return;// 发送 ADD_TODO 动作,携带必要数据dispatch({ type: 'ADD_TODO', text: inputText });setInputText(''); // 清空输入框};return (<div><inputvalue={inputText}onChange={(e) => setInputText(e.target.value)}placeholder="输入任务..."/><button onClick={handleAdd}>添加</button><ul>{todos.map(todo => (<likey={todo.id}style={{ textDecoration: todo.done ? 'line-through' : 'none' }}onClick={() => dispatch({ type: 'TOGGLE_TODO', id: todo.id })}>{todo.text}<button onClick={() => dispatch({ type: 'DELETE_TODO', id: todo.id })}>删除</button></li>))}</ul></div>);
}
复杂场景优势
当状态逻辑复杂时(例如多字段联动、条件判断多、状态依赖前一个状态),useReducer
比 useState
更清晰:
示例:购物车状态管理
// 初始状态
const initialState = {items: [], // 商品列表total: 0, // 总价count: 0 // 商品总数
};// 复杂状态逻辑的 reducer
function cartReducer(state, action) {switch (action.type) {case 'ADD_ITEM': {const existingItem = state.items.find(item => item.id === action.item.id);let newItems;if (existingItem) {// 商品已存在,更新数量newItems = state.items.map(item =>item.id === action.item.id ? { ...item, quantity: item.quantity + 1 } : item);} else {// 新商品,添加到列表newItems = [...state.items, { ...action.item, quantity: 1 }];}// 计算新的总数和总价const count = newItems.reduce((sum, item) => sum + item.quantity, 0);const total = newItems.reduce((sum, item) => sum + (item.price * item.quantity), 0);return { ...state, items: newItems, count, total };}case 'REMOVE_ITEM': {// 实现删除商品逻辑...}default:return state;}
}// 组件中使用
function ShoppingCart() {const [cart, dispatch] = useReducer(cartReducer, initialState);const addToCart = (product) => {dispatch({ type: 'ADD_ITEM', item: product });};// ...渲染购物车
}
与 useState
的对比
场景 | 推荐使用 | 原因 |
简单状态(单值) |
| 语法更简洁,无需定义 reducer |
复杂状态(多字段/联动) |
| 状态逻辑集中在 reducer 中,便于调试和复用,避免多个 |
需要预测状态变化 |
| action 可追踪,方便回溯状态变更历史 |
注意事项
- reducer 必须是纯函数:不能修改原 state、不能执行副作用(如请求、定时器),仅根据输入计算输出。
- 状态更新是批量的:多次
dispatch
会合并处理,类似setState
。 - 初始化状态:可直接传初始值,或通过函数返回(适合复杂初始化逻辑):
// 函数式初始化(只执行一次)
const [state, dispatch] = useReducer(reducer, null, () => {return { count: localStorage.getItem('count') || 0 };
});
通过 useReducer
,可以将复杂状态逻辑从组件中抽离,让组件更专注于 UI 渲染,同时使状态变化更可预测、易于测试。