为什么不光用 useState
// 多个相关 state
const [items, setItems] = useState([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [selected, setSelected] = useState<number | null>(null);
每个动作(开始加载 / 加载成功 / 加载失败 / 选中 / 清空)都得多次 setState、容易漏改、状态可能短暂处于不一致状态。
useReducer 把它们打包进一个 state + 一个 reducer 函数。
基本用法
type State = {
items: Item[];
loading: boolean;
error: string | null;
};
type Action =
| { type: 'load_start' }
| { type: 'load_success'; items: Item[] }
| { type: 'load_error'; message: string };
const initial: State = { items: [], loading: false, error: null };
function reducer(state: State, action: Action): State {
switch (action.type) {
case 'load_start':
return { ...state, loading: true, error: null };
case 'load_success':
return { ...state, loading: false, items: action.items };
case 'load_error':
return { ...state, loading: false, error: action.message };
}
}
function MyList() {
const [state, dispatch] = useReducer(reducer, initial);
useEffect(() => {
dispatch({ type: 'load_start' });
fetch('/api/items')
.then(r => r.json())
.then(items => dispatch({ type: 'load_success', items }))
.catch(err => dispatch({ type: 'load_error', message: err.message }));
}, []);
if (state.loading) return <p>加载中…</p>;
if (state.error) return <p>失败:{state.error}</p>;
return <ul>{state.items.map(...)}</ul>;
}
什么时候用 reducer 而不是 useState
| 用 useState | 用 useReducer |
|---|---|
| 1-2 个独立的 state | 多个 state 互相关联 |
| 改法简单 | 改法复杂、多种 action |
| 不想多写代码 | 想集中"业务规则" |
| 改的地方都在一个组件 | 改 state 的逻辑要复用 / 测试 |
经验:发现自己写很多 setX / setY / setZ 集体 setState,就该上 reducer。
reducer 是纯函数
输入 (state, action),输出 newState。不能 mutate state、不能调副作用:
// ❌
case 'add': state.items.push(action.item); return state;
// ✅
case 'add': return { ...state, items: [...state.items, action.item] };
副作用(fetch / setTimeout)放在 dispatch 之外。
配 Context = 轻量 Redux
const CartCtx = createContext<{
state: CartState;
dispatch: React.Dispatch<CartAction>;
} | null>(null);
export function CartProvider({ children }) {
const [state, dispatch] = useReducer(cartReducer, initialCart);
return (
<CartCtx.Provider value={{ state, dispatch }}>{children}</CartCtx.Provider>
);
}
后代组件:
const { state, dispatch } = useContext(CartCtx)!;
dispatch({ type: 'add', item });
这模式覆盖 80% 的"需要全局状态"场景——不用上 Redux。但Context value 变化导致所有消费者 re-render,性能敏感场景看 Zustand(第 15 篇)。
配 Immer 写"假 mutation"
复杂嵌套 state 用 useImmerReducer:
import { useImmerReducer } from 'use-immer';
function reducer(draft: State, action: Action) {
switch (action.type) {
case 'update_name':
draft.user.name = action.value; // 看起来在 mutate,其实是 immer 在背后造新对象
break;
}
}
单测友好
reducer 是纯函数,测试一行就够:
expect(reducer(initial, { type: 'load_success', items: [] })).toEqual({
items: [], loading: false, error: null,
});
→ 下一篇 自定义 Hook