首页 / React 19 入门教程 / Context + useReducer

React 19 入门教程

Context + useReducer

本教程共 50 篇 · 第 24 篇 · 更新于 2026-07-29 · 约 7 分钟阅读

ReactContextuseReducer状态管理dispatch

24. Context + useReducer

本节目标:将 Context 和 useReducer 组合,实现轻量级全局状态管理,无需引入第三方库。

为什么要组合

上一章 Context 解决了”读”的问题。但子组件想”改”数据,还得通过 props 传回调函数。

Context + useReducer 把”读”和”写”都放进 Context:

// 子组件既能读 state,也能 dispatch action
const { state, dispatch } = useContext(AppContext);

组合模式

核心思路:在父组件用 useReducer 管理状态,通过 Context 同时提供 state 和 dispatch。

// AppContext.js
import { createContext } from 'react';

export const AppContext = createContext(null);
// AppProvider.js
import { useReducer } from 'react';
import { AppContext } from './AppContext';
import { appReducer, initialState } from './reducer';

export function AppProvider({ children }) {
  const [state, dispatch] = useReducer(appReducer, initialState);

  return (
    <AppContext value={{ state, dispatch }}>
      {children}
    </AppContext>
  );
}
// main.jsx
import { AppProvider } from './AppProvider';

createRoot(document.getElementById('root')).render(
  <AppProvider>
    <App />
  </AppProvider>
);

完整示例:购物车

// cartReducer.js
export const initialState = { items: [], total: 0 };

export function cartReducer(state, action) {
  switch (action.type) {
    case 'add_item':
      const newItems = [...state.items, action.item];
      return {
        items: newItems,
        total: newItems.reduce((sum, item) => sum + item.price, 0)
      };
    case 'remove_item':
      const filtered = state.items.filter(i => i.id !== action.id);
      return {
        items: filtered,
        total: filtered.reduce((sum, item) => sum + item.price, 0)
      };
    case 'clear':
      return initialState;
    default:
      return state;
  }
}
// CartContext.js
import { createContext } from 'react';
export const CartContext = createContext(null);
// CartProvider.js
import { useReducer } from 'react';
import { CartContext } from './CartContext';
import { cartReducer, initialState } from './cartReducer';

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

  return (
    <CartContext value={{ cart: state, dispatch }}>
      {children}
    </CartContext>
  );
}
// ProductList.js
import { useContext } from 'react';
import { CartContext } from './CartContext';

function ProductList({ products }) {
  const { dispatch } = useContext(CartContext);

  return (
    <div>
      {products.map(product => (
        <div key={product.id}>
          {product.name} - ¥{product.price}
          <button onClick={() => dispatch({ type: 'add_item', item: product })}>
            加入购物车
          </button>
        </div>
      ))}
    </div>
  );
}
// CartSummary.js
import { useContext } from 'react';
import { CartContext } from './CartContext';

function CartSummary() {
  const { cart, dispatch } = useContext(CartContext);

  return (
    <div>
      <p>共 {cart.items.length} 件,合计 ¥{cart.total}</p>
      <button onClick={() => dispatch({ type: 'clear' })}>清空</button>
    </div>
  );
}

拆分 Context 优化性能

把 state 和 dispatch 放在一个 Context 里,state 变化时所有消费者都重新渲染,包括只读 dispatch 的组件。

优化方案:拆成两个 Context:

export const CartStateContext = createContext(null);
export const CartDispatchContext = createContext(null);

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

  return (
    <CartStateContext value={state}>
      <CartDispatchContext value={dispatch}>
        {children}
      </CartDispatchContext>
    </CartStateContext>
  );
}

只读 dispatch 的组件不会因 state 变化而重渲染。

什么时候拆分

消费者多、state 变化频繁时拆分。小应用一个 Context 够用。

封装自定义 Hook

每次用 useContext 都要判断 null,封装一下更省心:

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

// 使用时
const { cart, dispatch } = useCart();

适用场景

Context + useReducer 适合:

  • 中小型应用的全局状态
  • 不想引入 Redux/Zustand 的场景
  • 状态逻辑复杂但数据量不大的情况

不适合:

  • 超大型应用(考虑 Redux Toolkit)
  • 高频更新的数据(考虑 Zustand)
  • 需要持久化、中间件的场景

本节要点

  • Context 提供 state,useReducer 管理逻辑
  • { state, dispatch } 放进 Context value
  • 子组件通过 Context 读写,无需 props 传递
  • 大应用可拆分 state/dispatch 两个 Context