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