在 React 中,如果你想在组件树中共享状态(比如用户信息、登录状态等),就可以用「useContext + useReducer + useEffect」这组组合拳 🥊。
整体流程大致是这样的👇:
前端 dispatch → reducer 响应 action → 控制状态如何变化
这就像是一个简化版 Redux,但不需要额外的库。
🧩 一、context.tsx
创建上下文(Context),用来保存全局的用户状态以及更新状态的函数 dispatch。
1 2 3 4 5 6 7 8 9 10 11
| import { createContext, useContext, type Dispatch } from "react"; import type { UserState, UserAction } from "~/types/user";
export interface UserContextType { state: UserState; dispatch: Dispatch<UserAction>; }
export const UserContext = createContext<UserContextType | undefined>(undefined);
|
🧠 小结:
- state:存储用户信息
- dispatch:触发状态改变的函数
- UserContext:一个全局共享的数据源容器
🧱 二、provider.tsx
Provider 就像一个“外层包裹器”📦,负责提供上下文中的数据(state)和操作方法(dispatch)给整个应用。
1 2 3 4
| import React, { useContext, useEffect, useReducer } from "react"; import { UserContext, type UserContextType } from "./context"; import { userReducer } from "./reducer"; import type { UserState } from "~/types/user";
|
🌱 初始化用户状态
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| let initialState: UserState = { authenticated: false, user: { id: "", name: "", phone: "", province: "", city: "", email: "", title: "", organization: "", introduction: "", roleId: 2, roleName: "", roleDesc: "", maxCpu: 2, maxStorage: 5, maxJob: 3, isSuperAdmin: false, }, };
|
🧩 创建自定义 Hook
1 2 3 4 5 6
| export const useUserContext = () => { const context = useContext(UserContext); if (!context) throw new Error("useUserContext 必须在 UserProvider 中使用!"); return context; };
|
📌 这样任何组件都可以通过 useUserContext() 来获取全局用户状态。
💡 UserProvider 组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| export const UserProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { const [state, dispatch] = useReducer(userReducer, initialState, (init) => { try { const storedUser = localStorage.getItem("user"); const userId = localStorage.getItem("userId"); if (userId && storedUser) { return { authenticated: true, user: JSON.parse(storedUser), }; } } catch (e) { console.error(e); } return init; });
useEffect(() => { if (state.authenticated) { localStorage.setItem("user", JSON.stringify(state.user)); localStorage.setItem("userId", state.user.id); } else { localStorage.removeItem("user"); localStorage.removeItem("userId"); } }, [state]);
return ( <UserContext.Provider value={{ state, dispatch }}> {children} </UserContext.Provider> ); };
|
🧠 这里做了三件事:
- 使用 useReducer 管理状态;
- 用 useEffect 实现本地持久化(同步到 localStorage);
- 用 <UserContext.Provider> 将 state 和 dispatch 向全局提供。
⚙️ 三、reducer.tsx
reducer 是状态的“大脑” 🧠,
决定在不同的 action 下状态如何变化。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| import type { UserState, UserAction } from "~/types/user";
export function userReducer(state: UserState, action: UserAction): UserState { switch (action.type) { case "LOGIN": return { authenticated: true, user: action.payload };
case "LOGOUT": return { authenticated: false, user: { id: "", name: "", phone: "", province: "", city: "", email: "", title: "", organization: "", introduction: "", roleId: 2, roleName: "", roleDesc: "", maxCpu: 2, maxStorage: 5, maxJob: 3, isSuperAdmin: false, }, };
default: return state; } }
|
📘 小贴士:
- action.type 表示动作类型,比如 “LOGIN”、”LOGOUT”
- action.payload 是携带的数据
- reducer 必须是纯函数(不能直接修改 state)
🎨 四、使用示例
有了上面的 Provider,我们在页面中可以这样用👇:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| function LoginButton() { const { dispatch } = useUserContext();
return ( <button onClick={() => dispatch({ type: "LOGIN", payload: { id: "1", name: "Paxton" } }) } > 登录 </button> ); }
function UserInfo() { const { state } = useUserContext();
return ( <div> {state.authenticated ? ( <p>欢迎回来,{state.user.name}</p> ) : ( <p>请先登录 🙈</p> )} </div> ); }
|
然后在最外层包裹应用:
1 2 3
| <UserProvider> <App /> </UserProvider>
|
这样 App 中的所有组件都能共享登录状态 ✅。
🧭 整体关系图
1 2 3 4 5 6 7 8 9
| 组件 → dispatch(action) ↓ reducer(state, action) ↓ 新的 state ↓ Context Provider ↓ 所有 useUserContext() 的组件更新
|