大家好,我是程序员柏风,AI 最全的 Vibecoding 学习地图 http://coderoadmap.cn 作者。
Context 共享状态
这一课你将能做到
- 说出 Context 解决「props 一层层往下传」的麻烦
- 看懂 Provider + useContext 的基本结构
- 知道不要把所有数据都塞进 Context
为什么要学
主题色、当前用户、走法偏好——很多组件都要读。
若用 props 层层传递(prop drilling),中间层只为「转发」而存在,又烦又脆:Header 并不关心 user 怎么来的,却必须写 user={user} 往下递。
Context 提供「在树上的某处放一份值,深处直接读」。
人话:广播站。上面架天线(Provider),下面收音机(useContext)。
本站 AuthProvider 就是这类模式:登录态不必穿过每一层布局。
先建立图像
<AuthProvider> ← 提供 { user, setUser }
<Page>
<Header /> ← useContext 读取 user
<Main /> ← 不需要 user 的层不用接 props
</Page>
</AuthProvider>
没有 Provider 就调用 useContext,会拿到 createContext 时的默认值(常为 null),于是你要自己报错提醒。
跟着做
步骤 1:最小 Context(完整可抄)
import { createContext, useContext, useMemo, useState } from "react";
const AuthContext = createContext(null);
export function AuthProvider({ children }) {
const [user, setUser] = useState(null);
const value = useMemo(() => ({ user, setUser }), [user]);
return (
<AuthContext.Provider value={value}>{children}</AuthContext.Provider>
);
}
export function useAuth() {
const ctx = useContext(AuthContext);
if (!ctx) throw new Error("useAuth 必须在 AuthProvider 内");
return ctx;
}
export function WhoAmI() {
const { user } = useAuth();
return <span>{user ? user.name : "未登录"}</span>;
}
export function LoginButton() {
const { setUser } = useAuth();
return (
<button type="button" onClick={() => setUser({ name: "ada" })}>
演示登录
</button>
);
}
useMemo 避免每次渲染都新建 value 对象,导致所有消费者无谓更新。入门先知道「对象当 value 要小心」。
步骤 2:在树顶包起来
export function App() {
return (
<AuthProvider>
<WhoAmI />
<LoginButton />
</AuthProvider>
);
}
WhoAmI 不必从 App 接 user props。
步骤 3:先提升,不够再 Context
两个兄弟要同一份勾选:父组件 state 往往更简单,别一上来 Context。
跨很多层、很多页都要读的「当前用户 / 主题」再 Context。
步骤 4:拆分「值」与「dispatch」
高频更新的 value 与稳定的 setter 可分两个 Context,减少无关重渲染。入门先做一个 Provider 也行,知道有这招。
讲清楚要点
- 先状态提升,不够再 Context。
- Context 适合变化不疯狂的全局信息(主题、用户)。列表滚动位置、每秒心跳不要塞进去,会逼整片树重渲染。
- 按领域拆(Auth、Theme),避免一个上帝 Context。
- 自定义
useAuth:把「必须在 Provider 内」的检查收口,调用处干净。 - 本站读真实
AuthProvider对照,比再抄一套教程有用。
你可能会懵的地方
-
Context 是不是 Redux?
不是。Redux 等是更重的方案(时间旅行、中间件)。很多应用 Context + hooks 就够。不要为简历先上 Redux。 -
必须拆很多 Context 吗?
按变化频率和领域拆更好。主题很少变、聊天消息狂变,别捆在一个 value。 -
value 写成
value={{ user, setUser }}行不行?
能跑。但每次 Provider 渲染都是新对象。用useMemo更稳。 -
深层组件改了 Context,中间层会重渲染吗?
消费了该 Context 的会。没消费的不一定。仍不要把高频数据放进去。 -
Context 替代所有状态库?
简单共享可以;复杂高频更新要考虑拆分或专用库。
错法对照
| 错法 | 问题 | 正解 |
|---|---|---|
| 列表滚动位置进 Context | 狂渲染 | 本地 state |
| 无 Provider 就 useContext | 默认 null、白屏 | 树外包 Provider |
| 所有 API 数据进一个 store | 难维护 | 按页取数 + 少量全局 |
| 用 Context 代替 props 传标题 | 过度设计 | 父传子即可 |
| Provider 包太低 | 子树读不到 | 包在真正需要的祖先 |
| value 每次渲染新对象且未 memo | 下游全刷 | 稳定 value 或拆分 |
自测三问
- Context 主要减轻什么痛苦?什么时候用提升就够?
- 什么样的数据适合放 Context?举一个不适合的。
- 为何 Provider 的 value 常用 useMemo?
小结一句话
Context 向深处广播少量全局状态;先提升,再广播,别当垃圾桶。
动手加深
主题色 Context:Provider 给按钮,深层文本读取;确认不必层层 props。
跟着做(加厚 · react-context)
步骤:最小可运行对照
在 Vite 或 Next 练习页粘贴本课核心示例,改一个输入,确认屏幕变化符合预期。再故意写一个错法表里的写法,看控制台/界面如何坏,然后改回。
每个概念都问:state 在谁手里?UI 哪一块会因它重绘?
操作顺序:打开对应文件 → 改一处可观察点(文案、类型、className 或分支)→ 保存看结果 → 复原或留下有意义的小改动。
步骤:错法故意练一次
先按「用 jQuery 式直接改 DOM 冒充完成」做 30 秒(或在注释里写出若这样做会怎样),再改成「改数据(state/props)让 UI 推导出来」。 目的不是留坑,而是让失败现象进长期记忆。
你可能会懵的地方(加厚)
-
热更新后状态丢了?
正常:整树重挂会重置 state。用 key 或外提状态保留;开发期别被吓到。 -
和文档示例不一致?
以你锁的 React 大版本 + react.dev 为准;旧博客的 class 组件示例仅作历史阅读。 -
这课和前后课怎么接?
回看大纲上一课的小结一句、下一课标题;用自己的话写「本课产出什么可观察结果」。写不出来就重做跟着做。
错法对照(加厚)
| 错法 | 问题 | 正解 |
|---|---|---|
| 用 jQuery 式直接改 DOM 冒充完成 | 假完成/埋雷 | 改数据(state/props)让 UI 推导出来 |
| 只看不敲 | 第二天全忘 | 至少跑通本课步骤 |
| 跳过自测三问 | 以为会了 | 闭卷答再对稿 |
延伸
- Passing Data Deeply with Context
- 下一课:路由入门(URL 与页面)。