03-状态管理与路由——02. useContext + useReducer - 轻量级全局状态

📅 2026/6/16 11:54:07
03-状态管理与路由——02. useContext + useReducer - 轻量级全局状态
02. useContext useReducer - 轻量级全局状态一、5W1H 概述维度内容What组合 Context 和 useReducer 实现全局状态管理Why避免 props drilling集中管理复杂状态逻辑When跨多层组件共享状态、中等复杂度的应用Where创建 Context用 Provider 包裹组件树Who需要轻量级全局状态的开发者Howconst [state, dispatch] useReducer(reducer, initialState)createContext二、What - 什么是 Context useReducerContext useReducer 是 React 内置的组合模式用于管理跨组件共享的复杂状态。Context提供跨组件传递数据的能力useReducer管理复杂的状态更新逻辑// 核心结构 const AppContext createContext(); function AppProvider({ children }) { const [state, dispatch] useReducer(reducer, initialState); return AppContext.Provider value{{ state, dispatch }}{children}/AppContext.Provider; } function Component() { const { state, dispatch } useContext(AppContext); // 使用 state 和 dispatch }三、Why - 为什么需要这个组合3.1 解决 Props Drilling// ❌ 不好的做法层层传递 GrandParent user{user} Parent user{user} Child user{user} GrandChild user{user} / /Child /Parent /GrandParent // ✅ 好的做法使用 Context UserContext.Provider value{user} GrandParent Parent Child GrandChild / {/* 直接 useContext 获取 */} /Child /Parent /GrandParent /UserContext.Provider3.2 集中管理复杂状态当状态更新逻辑复杂时useReducer 比 useState 更清晰。3.3 无需第三方库对于中小型应用这是内置的轻量级方案。四、When - 何时使用场景是否使用说明主题深色/浅色模式✅ 推荐全局样式偏好用户认证信息✅ 推荐登录状态、用户资料语言/本地化✅ 推荐多语言切换跨多层组件共享状态✅ 推荐避免 props drilling复杂状态逻辑✅ 推荐useReducer 管理简单父子组件❌ 不推荐直接用 props大型应用⚠️ 谨慎考虑 Zustand/Redux五、Where - 在哪里使用独立的 store 目录存放 Context 和 Provider需要访问状态的组件中使用 useContextsrc/ ├── store/ │ ├── AppContext.js # Context 定义 │ └── AppProvider.js # Provider 组件 ├── components/ │ └── Component.jsx # 使用 useContext └── App.jsx # 包裹 Provider六、Who - 谁需要使用需要跨组件共享状态且不想引入第三方库的 React 开发者。七、How - 如何使用7.1 基础实现// store/AppContext.js import React, { createContext, useContext, useReducer } from react; // 1. 定义初始状态 const initialState { user: null, theme: light, count: 0 }; // 2. 定义 Action 类型 const ACTIONS { SET_USER: SET_USER, TOGGLE_THEME: TOGGLE_THEME, INCREMENT: INCREMENT, DECREMENT: DECREMENT }; // 3. 定义 Reducer function appReducer(state, action) { switch (action.type) { case ACTIONS.SET_USER: return { ...state, user: action.payload }; case ACTIONS.TOGGLE_THEME: return { ...state, theme: state.theme light ? dark : light }; case ACTIONS.INCREMENT: return { ...state, count: state.count 1 }; case ACTIONS.DECREMENT: return { ...state, count: state.count - 1 }; default: return state; } } // 4. 创建 Context const AppContext createContext(); // 5. Provider 组件 export function AppProvider({ children }) { const [state, dispatch] useReducer(appReducer, initialState); // 使用 useMemo 优化性能 const value useMemo(() ({ state, dispatch }), [state]); return AppContext.Provider value{value}{children}/AppContext.Provider; } // 6. 自定义 Hook export function useAppContext() { const context useContext(AppContext); if (!context) { throw new Error(useAppContext must be used within AppProvider); } return context; }7.2 使用状态// components/Counter.jsx import { useAppContext } from ../store/AppContext; function Counter() { const { state, dispatch } useAppContext(); return ( div p计数: {state.count}/p button onClick{() dispatch({ type: INCREMENT })}/button button onClick{() dispatch({ type: DECREMENT })}-/button /div ); } // components/ThemeToggle.jsx function ThemeToggle() { const { state, dispatch } useAppContext(); return ( button onClick{() dispatch({ type: TOGGLE_THEME })} 当前主题: {state.theme} /button ); } // components/UserProfile.jsx function UserProfile() { const { state, dispatch } useAppContext(); const login () { dispatch({ type: SET_USER, payload: { name: 张三, email: zhangexample.com } }); }; const logout () { dispatch({ type: SET_USER, payload: null }); }; if (!state.user) { return button onClick{login}登录/button; } return ( div p欢迎, {state.user.name}/p button onClick{logout}退出/button /div ); } // App.jsx function App() { return ( AppProvider ThemeToggle / Counter / UserProfile / /AppProvider ); }7.3 Todo 应用完整示例// store/TodoContext.jsx import React, { createContext, useContext, useReducer } from react; const initialState { todos: [], filter: all, // all, active, completed loading: false, error: null }; const ACTIONS { ADD_TODO: ADD_TODO, TOGGLE_TODO: TOGGLE_TODO, DELETE_TODO: DELETE_TODO, EDIT_TODO: EDIT_TODO, SET_FILTER: SET_FILTER, SET_LOADING: SET_LOADING, SET_ERROR: SET_ERROR, LOAD_TODOS: LOAD_TODOS }; function todoReducer(state, action) { switch (action.type) { case ACTIONS.ADD_TODO: return { ...state, todos: [...state.todos, action.payload] }; case ACTIONS.TOGGLE_TODO: return { ...state, todos: state.todos.map(todo todo.id action.payload ? { ...todo, completed: !todo.completed } : todo ) }; case ACTIONS.DELETE_TODO: return { ...state, todos: state.todos.filter(todo todo.id ! action.payload) }; case ACTIONS.EDIT_TODO: return { ...state, todos: state.todos.map(todo todo.id action.payload.id ? { ...todo, text: action.payload.text } : todo ) }; case ACTIONS.SET_FILTER: return { ...state, filter: action.payload }; case ACTIONS.SET_LOADING: return { ...state, loading: action.payload }; case ACTIONS.SET_ERROR: return { ...state, error: action.payload }; case ACTIONS.LOAD_TODOS: return { ...state, todos: action.payload, loading: false }; default: return state; } } const TodoContext createContext(); export function TodoProvider({ children }) { const [state, dispatch] useReducer(todoReducer, initialState); // 异步操作封装 const loadTodos async () { dispatch({ type: ACTIONS.SET_LOADING, payload: true }); try { const response await fetch(/api/todos); const data await response.json(); dispatch({ type: ACTIONS.LOAD_TODOS, payload: data }); } catch (error) { dispatch({ type: ACTIONS.SET_ERROR, payload: error.message }); } }; const value { state, dispatch, loadTodos }; return TodoContext.Provider value{value}{children}/TodoContext.Provider; } export function useTodo() { const context useContext(TodoContext); if (!context) { throw new Error(useTodo must be used within TodoProvider); } return context; }// components/TodoInput.jsx function TodoInput() { const [text, setText] useState(); const { dispatch } useTodo(); const handleSubmit (e) { e.preventDefault(); if (text.trim()) { dispatch({ type: ADD_TODO, payload: { id: Date.now(), text, completed: false } }); setText(); } }; return ( form onSubmit{handleSubmit} input value{text} onChange{(e) setText(e.target.value)} / button typesubmit添加/button /form ); } // components/TodoList.jsx function TodoList() { const { state, dispatch } useTodo(); const getFilteredTodos () { switch (state.filter) { case active: return state.todos.filter(t !t.completed); case completed: return state.todos.filter(t t.completed); default: return state.todos; } }; return ( ul {getFilteredTodos().map(todo ( li key{todo.id} input typecheckbox checked{todo.completed} onChange{() dispatch({ type: TOGGLE_TODO, payload: todo.id })} / span{todo.text}/span button onClick{() dispatch({ type: DELETE_TODO, payload: todo.id })} 删除 /button /li ))} /ul ); } // components/TodoFilter.jsx function TodoFilter() { const { state, dispatch } useTodo(); const filters [all, active, completed]; return ( div {filters.map(filter ( button key{filter} onClick{() dispatch({ type: SET_FILTER, payload: filter })} style{{ fontWeight: state.filter filter ? bold : normal }} {filter} /button ))} /div ); } // components/TodoStats.jsx function TodoStats() { const { state } useTodo(); const total state.todos.length; const completed state.todos.filter(t t.completed).length; return ( div p总计: {total} | 已完成: {completed} | 未完成: {total - completed}/p {completed 0 ( button onClick{() dispatch({ type: CLEAR_COMPLETED })} 清除已完成 /button )} /div ); }八、性能优化8.1 拆分 Context// ❌ 所有状态放在一个 Context 中 const AppContext createContext(); // 任何状态变化都会导致所有消费者重渲染 // ✅ 按职责拆分 const UserContext createContext(); const ThemeContext createContext(); const NotificationContext createContext();8.2 使用 useMemo 缓存 valuefunction AppProvider({ children }) { const [state, dispatch] useReducer(reducer, initialState); // 缓存 value避免不必要的重渲染 const value useMemo(() ({ state, dispatch }), [state]); return AppContext.Provider value{value}{children}/AppContext.Provider; }8.3 选择性订阅// 只订阅需要的状态 function UserName() { const { state } useAppContext(); // 只在 user.name 变化时重渲染 return div{state.user?.name}/div; } // 更好的方式使用 selector function useSelector(selector) { const { state } useAppContext(); return selector(state); }九、优缺点分析优点✅ 内置方案无需额外依赖✅ 类型安全TypeScript 友好✅ 适合中小型应用✅ 逻辑集中易于调试缺点❌ 性能需要手动优化❌ 代码量比 Zustand 多❌ 没有内置异步支持❌ 大型应用可能不够用十、练习题实现一个购物车状态管理添加、删除、修改数量实现一个主题切换 用户登录状态管理十一、小结要点说明适用场景中小型应用、跨层级状态核心组合Context useReducer性能优化拆分 Context、useMemo最佳实践封装自定义 Hook