在 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 使用。