视频版(播客风格更精彩)
🎙 欢迎来到《前端达人 · 播客书单》第 26 期。
本期我们将聚焦 React 应用中最常见但也最容易误用的机制:Context API。 你可能早就知道它能“解决 props drilling”,但它到底该怎么用?用在哪儿最合理?项目中真的能撑起状态管理吗?
今天我们不讲概念,而是用一个完整业务场景来串讲:
登录状态管理 + 权限控制 + 页面加载指示
一、项目中的真实需求长什么样?
想象你开发一个中后台管理系统,有如下需求:
登录成功后,所有页面都能获取当前用户信息
根据权限展示不同侧边栏菜单
请求过程中显示全局 loading spinner
页面 Header 需要展示用户名
子页面(如 /dashboard)要能触发权限刷新
这时候你该怎么做?
如果你还在层层 props 传递,或者每个组件都重新调用 API 获取用户数据…… ❌ 你的状态架构可能存在问题!
二、什么才是“值得被共享”的状态?
我们用一句话定义「共享状态」:
跨越多个组件层级,且多个组件都要“读取或依赖”的状态
在本项目中,有三类典型共享状态:
状态类型 | 示例字段 | 使用位置 |
---|---|---|
用户信息 | user.name , | Header / Sidebar / ProtectedRoute |
权限数组 | permissions: string[] | Sidebar 菜单 / 页面权限判断 |
全局状态 | loading , | 按钮、跳转、加载框等全局控制点 |
三、手把手搭建 Context 架构(业务驱动)
我们来实现一个可复用的 AppContext
,管理上面提到的状态 👇
1️⃣ 定义 State & Action 类型
type State = {
user: { id: string; name: string } | null;
permissions: string[];
loading: boolean;
};
type Action =
| { type: "login"; user: State["user"] }
| { type: "set_permissions"; permissions: string[] }
| { type: "set_loading"; loading: boolean };
2️⃣ 编写 reducer 函数(逻辑集中)
function reducer(state: State, action: Action): State {
switch (action.type) {
case"login":
return { ...state, user: action.user };
case"set_permissions":
return { ...state, permissions: action.permissions };
case"set_loading":
return { ...state, loading: action.loading };
default:
return state;
}
}
3️⃣ 创建 Context & Provider
const AppContext = createContext<{
state: State;
dispatch: React.Dispatch<Action>;
} | null>(null);
exportfunction AppProvider({ children }: { children: React.ReactNode }) {
const [state, dispatch] = useReducer(reducer, {
user: null,
permissions: [],
loading: false,
});
return (
<AppContext.Provider value={{ state, dispatch }}>
{children}
</AppContext.Provider>
);
}
四、组件消费场景(代码实战)
✅ 场景 1:Header 显示用户名
const Header = () => {
const { state } = useContext(AppContext);
return <div>👋 Welcome, {state.user?.name ?? "Guest"}</div>;
};
✅ 场景 2:Sidebar 根据权限展示菜单
const Sidebar = () => {
const { state } = useContext(AppContext);
const menu = state.permissions.includes("admin")
? ["Dashboard", "User Management"]
: ["Dashboard"];
return <Menu items={menu} />;
};
✅ 场景 3:登录按钮触发全局 dispatch
const LoginButton = () => {
const { dispatch } = useContext(AppContext);
const handleLogin = async () => {
dispatch({ type: "set_loading", loading: true });
const user = await fakeLoginAPI();
dispatch({ type: "login", user });
const perms = await fetchUserPermissions(user.id);
dispatch({ type: "set_permissions", permissions: perms });
dispatch({ type: "set_loading", loading: false });
};
return <button onClick={handleLogin}>登录</button>;
};
五、最佳实践建议(生产环境角度)
✅ 推荐做法:
将 useContext 封装为自定义 Hook:
export function useAppContext() {
const ctx = useContext(AppContext);
if (!ctx) throw new Error("Context 未初始化!");
return ctx;
}
拆分不同功能 Context(例如:AuthContext, ThemeContext)
搭配 useReducer 管理复杂结构
避免频繁变化的状态进入 Context(如输入框内容)
❌ 不推荐做法:
把整个业务状态都塞进一个大 Context
在组件树很浅时使用 Context(props 更简单)
在 Provider 外部调用 useContext(容易 undefined)
🧠 小结复盘
Context API 是「解决跨组件状态共享」的工具,不是通用状态管理库
推荐用于 “读多写少” 的全局数据:用户、权限、UI偏好等
搭配 useReducer + TypeScript 可大幅增强可维护性
对于高频变动 + 异步 + 缓存场景,建议结合 React Query、Redux Toolkit 使用
#React #React播客 #前端播客 #前端达人 #TypeScript