在 React 中,Context API + useReducer 是一种轻量级的状态管理方案,适合中小型应用或需要跨组件共享复杂状态的场景。它避免了 Redux 的繁琐配置,同时提供了清晰的状态更新逻辑。
1. 基本使用步骤
(1) 定义 Reducer
类似于 Redux 的 reducer,用于处理状态更新逻辑:
// reducer.js
export const initialState = {
count: 0,
user: null,
};
export function reducer(state, action) {
switch (action.type) {
case 'INCREMENT':
return { ...state, count: state.count + 1 };
case 'DECREMENT':
return { ...state, count: state.count - 1 };
case 'SET_USER':
return { ...state, user: action.payload };
default:
return state;
}
}
(2) 创建 Context 和 Provider
使用 createContext
创建 Context,并用 useReducer
管理状态:
// AppContext.js
import { createContext, useReducer } from 'react';
import { initialState, reducer } from './reducer';
// 1. 创建 Context
export const AppContext = createContext();
// 2. 创建 Provider 组件
export function AppProvider({ children }) {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<AppContext.Provider value={{ state, dispatch }}>
{children}
</AppContext.Provider>
);
}
(3) 在顶层组件包裹 Provider
// App.js
import { AppProvider } from './AppContext';
import Counter from './Counter';
function App() {
return (
<AppProvider>
<Counter />
</AppProvider>
);
}
(4) 在子组件使用状态
通过 useContext
获取 state
和 dispatch
:
// Counter.js
import { useContext } from 'react';
import { AppContext } from './AppContext';
export function Counter() {
const { state, dispatch } = useContext(AppContext);
return (
<div>
<p>Count: {state.count}</p>
<button onClick={() => dispatch({ type: 'INCREMENT' })}>+</button>
<button onClick={() => dispatch({ type: 'DECREMENT' })}>-</button>
</div>
);
}
2. 数据持久化
Context + useReducer 管理的状态是纯内存状态,当页面刷新时,这些数据会丢失,因为 JavaScript 的内存会被清空,恢复到初始状态。
在 React 的 Context + useReducer 架构中实现数据持久化( localStorage
sessionStorage
,页面刷新不丢失)
2.1实现步骤
(1)定义 Reducer(持久化/非持久化/临时数据)
// reducer.js
export const initialState = {
// 需要持久化的数据
persistedData: { language: 'en' },
// 非持久化数据
sessionData: { loginForm: null },
// 临时数据
tempData: {
count: 0
}
};
export function reducer(state, action) {
switch (action.type) {
// 持久化数据
case 'SET_LANGUAGE':
return {
...state,
persistedData: { ...state.persistedData, language: action.payload }
};
// 非持久化数据
case 'SET_LOGIN_FORM':
return {
...state,
sessionData: { ...state.sessionData, loginForm: action.payload }
};
// 临时数据
case 'SET_COUNT':
return {
...state,
tempData: { ...state.tempData, count: action.payload }
}
default:
return state;
}
}
(2)带持久化的Provider组件实现
// AppContext.jsx
import { createContext, useReducer, useEffect } from 'react';
import { initialState, reducer } from './reducer';
// 1. 创建 Context
export const AppContext = createContext();
// 2. 创建 Provider 组件
export function AppProvider({ children }) {
const [state, dispatch] = useReducer(
reducer,
{
...initialState,
persistedData: JSON.parse(localStorage.getItem('persistedData')) || initialState.persistedData,
sessionData: JSON.parse(sessionStorage.getItem('sessionData')) || initialState.sessionData
}
);
// 监听localStorage字段的变化
useEffect(() => {
localStorage.setItem('persistedData', JSON.stringify(state.persistedData));
}, [state.persistedData]);
// 监听sessionStorage 字段的变化
useEffect(() => {
sessionStorage.setItem('sessionData', JSON.stringify(state.sessionData));
}, [state.sessionData]);
return (
<AppContext.Provider value={{ state, dispatch }}>
{children}
</AppContext.Provider>
);
}
(3)在顶层组件包裹 Provider
// app.jsx
import { RouterProvider } from "react-router";
import router from "./router/index.jsx";
import { AppProvider } from '@stores/AppContext.jsx';
function App() {
return (
<AppProvider>
<div className="app">
<RouterProvider router={router} />
</div>
</AppProvider>
)
}
export default App
2.2 纯 localStorage 与 Context + useReducer + localStorage对比
1、 纯 localStorage特点
优点 | 缺点 |
---|---|
1. 实现简单,无需额外库 | 1. 状态不同步:多个组件无法实时共享同一份数据 |
2. 适合极简场景 | 2. 重复代码:每个组件需单独处理存储逻辑 |
3. 无性能开销(仅读写存储) | 3. 难以维护:业务复杂时逻辑分散 |
2、 Context + useReducer + localStorage特点
优点 | 缺点 |
---|---|
1. 状态全局共享:所有组件实时响应变化 | 1. 代码量稍多(需设置 Context/Reducer) |
2. 逻辑集中:易于维护和扩展 | 2. 小型项目可能过度设计 |
3. 自动持久化:状态变更自动同步到存储 | 3. 需处理 Provider 嵌套问题 |
核心区别对比
对比维度 | 纯 localStorage | Context + useReducer + localStorage |
---|---|---|
状态同步 | 需手动触发,组件间不同步 | 自动同步,全局状态一致 |
代码组织 | 逻辑分散在各组件 | 集中管理,高内聚低耦合 |
维护性 | 难扩展,易出现重复代码 | 易于扩展和维护 |
性能 | 直接操作存储,无额外开销 | 有 Context 的渲染开销(可通过 memo 优化) |
适用场景 | 简单页面、独立组件 | 中大型应用、需共享状态的场景 |
总结
- 直接 localStorage:简单粗暴,适合局部状态。
- Context + useReducer + localStorage:专业方案,适合全局状态。
2.3注意事项
localStorage
/sessionStorage
只能存字符串,复杂数据需用JSON.stringify
。- 优点:1、代码简洁,适合小型应用;2、无需第三方库;
- 缺点: 1、
localStorage
/sessionStorage
是同步操作,可能阻塞主线程;2、存储大小有限(通常 5MB);
2. 进阶优化
(1) 封装自定义 Hook
避免在每个组件里重复写 useContext
:
// hooks/useAppContext.js
import { useContext } from 'react';
import { AppContext } from '../AppContext';
export function useAppContext() {
return useContext(AppContext);
}
然后在组件中使用:
const { state, dispatch } = useAppContext();
(2) 优化性能(避免不必要的渲染)
默认情况下,Context
的更新会导致所有消费者组件重新渲染。可以使用 memo
+ 拆分 Context 优化:
// 拆分多个 Context
const CountContext = createContext();
const UserContext = createContext();
// 在 Provider 里分别提供
<CountContext.Provider value={{ countState, countDispatch }}>
<UserContext.Provider value={{ userState, userDispatch }}>
{children}
</UserContext.Provider>
</CountContext.Provider>
(3) 结合异步操作
可以在 dispatch
里处理异步逻辑(如 API 请求):
async function fetchUser(dispatch) {
try {
const user = await fetch('/api/user').then(res => res.json());
dispatch({ type: 'SET_USER', payload: user });
} catch (error) {
dispatch({ type: 'SET_ERROR', payload: error.message });
}
}
// 在组件中调用
fetchUser(dispatch);
3. 优缺点对比
优点 | 缺点 |
---|---|
无需额外库,React 原生支持 | 大型应用可能性能较差(需手动优化) |
比 Redux 更轻量 | 异步处理较麻烦(需手动封装) |
适合中小型应用 | 调试不如 Redux 方便(无 DevTools) |
4. 适用场景
- 小型/中型应用:不想引入 Redux 或 Zustand 时。
- 组件层级较深:需要跨多层传递状态时。
- 简单全局状态:如主题、用户登录信息等。
总结
Context + useReducer
是 React 内置的状态管理方案,适合轻量级需求。- 对于更复杂的状态管理,可考虑 Zustand 或 Redux Toolkit。
- 如果涉及大量异步逻辑,建议结合 React Query 或 SWR 使用。