人人都会AI编程

useReducer:复杂状态管理

更新时间:2026-07-10

当组件的状态逻辑变得复杂——比如涉及多个子值、相互依赖的状态更新,或者下一次状态依赖于上一次状态的值——useState 的分散声明和更新函数会让代码变得难以追踪。useReducer 是 React 为这类场景提供的替代方案,它将状态更新逻辑集中到一个名为 reducer 的纯函数中。

useReducer 与 useState 的关系

可以把 useReducer 理解为 useState 的“升级版”。useState 适合简单的独立状态,useReducer 适合状态之间有关联、更新逻辑复杂的场景。实际上,useState 的底层就是用 useReducer 实现的。

两者的核心区别在于状态更新方式:

  • useState:直接调用 setter 传入新值。
  • useReducer:通过 dispatch 派发一个 action 对象,由 reducer 函数根据 action 类型计算新状态。

基本语法

const [state, dispatch] = useReducer(reducer, initialState, init?);
  • reducer:一个纯函数 (state, action) => newState,接收当前状态和一个 action 对象,返回计算后的新状态。
  • initialState:初始状态值。
  • init(可选):惰性初始化函数,用于计算初始状态,避免在每次渲染时重复创建。
  • 返回值:当前状态 state 和一个 dispatch 函数,用来派发 action。

典型的 reducer 函数

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 };
    case 'decrement':
      return { count: state.count - 1 };
    case 'reset':
      return { count: action.payload };
    default:
      throw new Error('Unknown action type');
  }
}

Action 通常是一个包含 type 字段的对象,可以携带额外的数据放在 payload 中。约定俗成的模式来自于 Redux,但 React 本身对 action 的形状没有强制要求。

何时使用 useReducer

适用场景:

  • 状态逻辑复杂:一个操作会同时改变多个状态字段,或者不同的操作以不同的方式改变同一状态。
  • 下次状态依赖上次状态:比如计数器、多步骤表单、购物车总价计算等。
  • 状态更新逻辑需要被复用:reducer 是纯函数,可以单独提取、测试和复用。
  • 需要清晰追踪状态变更:通过 action 类型可以明确知道是什么触发了变化,便于调试和维护。

不适用场景:

  • 简单的独立状态(如一个布尔 toggle、一个输入框的值),直接使用 useState 即可,引入 reducer 反而增加样板代码。

实战示例:购物车状态管理

// 定义 action 类型常量(非必须,但便于维护)
const ADD_ITEM = 'ADD_ITEM';
const REMOVE_ITEM = 'REMOVE_ITEM';
const CLEAR_CART = 'CLEAR_CART';

const initialCart = [];

function cartReducer(state, action) {
  switch (action.type) {
    case ADD_ITEM: {
      const existingItem = state.find(item => item.id === action.payload.id);
      if (existingItem) {
        return state.map(item =>
          item.id === action.payload.id
            ? { ...item, quantity: item.quantity + 1 }
            : item
        );
      }
      return [...state, { ...action.payload, quantity: 1 }];
    }
    case REMOVE_ITEM:
      return state.filter(item => item.id !== action.payload);
    case CLEAR_CART:
      return [];
    default:
      return state;
  }
}

function ShoppingCart() {
  const [cart, dispatch] = useReducer(cartReducer, initialCart);

  const addItem = (product) => {
    dispatch({ type: ADD_ITEM, payload: product });
  };

  const removeItem = (productId) => {
    dispatch({ type: REMOVE_ITEM, payload: productId });
  };

  const clearCart = () => {
    dispatch({ type: CLEAR_CART });
  };

  return (
    <div>
      <h2>购物车 ({cart.length} 种商品)</h2>
      <ul>
        {cart.map(item => (
          <li key={item.id}>
            {item.name} x {item.quantity}
            <button onClick={() => removeItem(item.id)}>删除</button>
          </li>
        ))}
      </ul>
      <button onClick={clearCart}>清空购物车</button>
      {/* 模拟添加商品 */}
      <button onClick={() => addItem({ id: 1, name: 'React 实战', price: 99 })}>
        添加《React 实战》
      </button>
    </div>
  );
}

在这个例子中,所有的购物车逻辑(添加、去重、累加、删除、清空)都集中在 cartReducer 中,组件里只需调用 dispatch 派发对应的 action,状态变更逻辑清晰可预测。如果将来添加新的操作(如修改数量),只需在 reducer 中增加一个 case,不会影响组件的其他部分。

惰性初始化

当初始状态的创建成本较高(例如需要从 localStorage 读取或进行复杂计算),可以使用第三个参数 init 函数进行惰性初始化,避免每次渲染都重新计算:

function init(initialValue) {
  // 从本地存储读取,或进行其他计算
  const savedCart = localStorage.getItem('cart');
  return savedCart ? JSON.parse(savedCart) : initialValue;
}

const [cart, dispatch] = useReducer(cartReducer, initialCart, init);

init 接收 initialCart 作为参数,仅在首次渲染时调用一次。

与 useContext 结合:轻量级全局状态管理

useReduceruseContext 结合,可以实现无需第三方库的全局状态管理,非常适合中小型应用。

// CartContext.jsx
const CartContext = createContext();

export function CartProvider({ children }) {
  const [cart, dispatch] = useReducer(cartReducer, initialCart);

  return (
    <CartContext.Provider value={{ cart, dispatch }}>
      {children}
    </CartContext.Provider>
  );
}

export function useCart() {
  const context = useContext(CartContext);
  if (!context) {
    throw new Error('useCart 必须在 CartProvider 内部使用');
  }
  return context;
}

任何被 CartProvider 包裹的组件都可以通过 useCart() 获取 cartdispatch,实现跨层级的购物车状态共享。这种方式比引入 Redux 更加轻量,且逻辑依然清晰可控。

useReducer 的优势

  1. 可预测性:状态更新逻辑集中在 reducer 中,遵循 (state, action) => newState 的模式,易于推理。
  2. 可测试性:reducer 是纯函数,测试时只需提供当前状态和 action,断言返回的新状态。
  3. 可维护性:当状态更新逻辑逐渐膨胀时,分散的多个 useStatesetState 调用容易失控,而 reducer 将所有分支集中管理,甚至可按业务拆分多个 reducer 再合并。
  4. 调试友好:可以轻松记录每次 dispatch 的 action 和前后的 state,配合 Redux DevTools 也可以进行时间旅行调试(需要额外桥接)。

注意事项

  • reducer 必须是纯函数:不能修改原 state,必须返回一个新对象;不能在 reducer 中执行副作用(如请求数据、设置定时器)。副作用应该放在组件或在 dispatch 之前处理。
  • action 设计不要过度抽象:虽然 action 可以是一个字符串,但推荐使用具有描述性的字符串常量或更结构化的对象,保持代码可读性。
  • 不要将所有状态都放进一个 reducer:可以采用多个 useReducer,每个 reducer 管理一类状态,保持 reducer 函数的简洁和专注。

总结

useReducer 是 React 内置的解决复杂状态管理的利器。当 useState 开始导致状态更新逻辑四散各处、难以追踪时,就是引入 useReducer 的明确信号。它用 reducer 函数集中管理状态变更规则,让组件代码回归“触发意图(dispatch)”和“渲染状态(state)”的清晰分离,是迈向更可维护状态管理的第一步。