为什么不光用 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