使用 useReducer 管理复杂状态
使用 reducer 函数建模状态转换,分发操作对象,并在状态逻辑变得复杂时优先使用 useReducer 而不是 useState。
使用 useReducer 管理复杂状态 是 CoddyKit 上的免费 Frontend Academy 课时。 这是第 2 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Frontend Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Frontend Academy 课程共包含 4 节课。
当 useState 不够用时
当状态转换包含复杂逻辑、多个相关值,或新状态以非简单方式依赖前一个状态时,相比多次调用 useState,useReducer 是更简洁的选择。
归约函数模式
归约函数是一个纯函数:(state, action) => newState。它接收当前状态和一个操作对象,并返回下一个状态。不要进行修改——始终返回一个新对象。
type Action =
| { type: 'increment' }
| { type: 'decrement' }
| { type: 'reset'; payload: number };
function counterReducer(state: number, action: Action): number {
switch (action.type) {
case 'increment': return state + 1;
case 'decrement': return state - 1;
case 'reset': return action.payload;
default: return state;
}
}useReducer 语法
useReducer(reducer, initialState) 会返回当前状态和一个 dispatch 函数。使用操作对象调用 dispatch,以触发状态转换。
function Counter() {
const [count, dispatch] = useReducer(counterReducer, 0);
return (
<div>
<p>{count}</p>
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
<button onClick={() => dispatch({ type: 'reset', payload: 0 })}>Reset</button>
</div>
);
}类型化操作——可辨识联合类型
对操作使用 TypeScript 可辨识联合类型,以便在归约函数的 switch 语句中获得完备的类型检查。
type CartAction =
| { type: 'ADD_ITEM'; item: CartItem }
| { type: 'REMOVE_ITEM'; id: string }
| { type: 'SET_QUANTITY'; id: string; qty: number }
| { type: 'CLEAR' };复杂状态示例:购物车
需要执行添加、移除和更新数量操作的购物车,是使用 useReducer 的理想场景。
interface CartState {
items: CartItem[];
total: number;
}
function cartReducer(state: CartState, action: CartAction): CartState {
switch (action.type) {
case 'ADD_ITEM': {
const exists = state.items.find(i => i.id === action.item.id);
if (exists) {
const items = state.items.map(i =>
i.id === action.item.id ? { ...i, qty: i.qty + 1 } : i
);
return { ...state, items, total: calcTotal(items) };
}
const items = [...state.items, { ...action.item, qty: 1 }];
return { ...state, items, total: calcTotal(items) };
}
case 'CLEAR': return { items: [], total: 0 };
default: return state;
}
}分发函数是稳定的
dispatch 函数是稳定的——它在不同渲染之间不会改变。将它作为属性传递,或将它用作 useEffect 依赖项都是安全的,不会因此触发重复执行。
组合使用 useReducer 与 useContext
一种强大的模式是:让上下文同时提供状态和分发函数,使任何消费者都能读取状态或分发操作,而无需逐层传递属性。
const CartContext = createContext<{ state: CartState; dispatch: Dispatch<CartAction> } | null>(null);
function CartProvider({ children }: { children: React.ReactNode }) {
const [state, dispatch] = useReducer(cartReducer, { items: [], total: 0 });
return <CartContext.Provider value={{ state, dispatch }}>{children}</CartContext.Provider>;
}useReducer 与 useState
在以下情况下请选择 useReducer:状态包含多个子值;下一个状态以复杂方式依赖前一个状态;转换很多且需要明确命名;或者您希望将状态逻辑与组件分开测试。
单独测试归约函数
由于归约函数是纯函数,因此无需渲染任何组件就能轻松测试。
test('increment action increases count', () => {
expect(counterReducer(0, { type: 'increment' })).toBe(1);
expect(counterReducer(5, { type: 'increment' })).toBe(6);
});
test('reset action sets count to payload', () => {
expect(counterReducer(42, { type: 'reset', payload: 0 })).toBe(0);
});使用 Immer 进行不可变更新
immer 库允许您编写看起来像是在修改状态的代码,同时生成新的不可变状态。它适合在归约函数中更新复杂的嵌套对象。
快速检查
归约函数的函数签名是什么?
总结:useReducer
(state, action) => newState——纯函数,不进行修改。useReducer(reducer, initial) 返回 [state, dispatch]。使用可辨识联合类型的操作类型来提高安全性。分发函数是稳定的。它非常适合包含大量状态转换的复杂状态。将它与 useContext 结合,可进行全局状态管理。将归约函数作为纯函数单独测试。
常见问题解答
「使用 useReducer 管理复杂状态」课时是免费的吗?
是的 — 「使用 useReducer 管理复杂状态」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Frontend Academy 课程的其余内容,请升级到 CoddyKit PRO。 Frontend Academy 课程共包含 4 节课。
「使用 useReducer 管理复杂状态」这节课中我会学到什么?
使用 reducer 函数建模状态转换,分发操作对象,并在状态逻辑变得复杂时优先使用 useReducer 而不是 useState。 你通过在浏览器中直接运行的动手代码来练习 Frontend Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 Frontend Academy 需要有经验吗?
无需任何先前经验。CoddyKit 上的 Frontend Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 2 节课,共 4 节。
「使用 useReducer 管理复杂状态」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 Frontend Academy 课中编写并运行代码吗?
能。每节 Frontend Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。
此课程中的所有课时
- 使用 useContext 管理全局状态
- 使用 useReducer 管理复杂状态
- 使用 useMemo 与 useCallback 提升性能
- 自定义 Hook:提取可复用逻辑