R19第 1 / 1 课时未开始内容版本 1.0.0 · 核验于 2026-10-26

Context 与依赖范围

Context 与依赖范围 · 阅读后完成下方实验与检查题,再用「标记完成」记录进度。

学习目标

主题、语言、学习偏好这类「很多组件都要、又懒得逐层传」的数据,正适合 Context。读完你应该能用 createContext + useContext 搭出主题切换,分得清什么时候该用 Context、什么时候属性传递更好,并会控制 Provider 的作用范围。

问题场景

设置面板里有个主题开关,偏好要传给三层之下的卡片组件。属性传递的写法是层层转发:

<Settings theme={theme} onToggle={...} />
// Settings 内部
<Card theme={theme} onToggle={...} />
// Card 内部
<ThemeButton theme={theme} onToggle={...} />

中间的 Settings 和 Card 自己根本不用 theme,却被迫接住再转手——这就是「属性逐层下钻」。任何一层重构签名,上下层全得跟着改。

核心解释

Context 让顶层声明、深处直接取用,中间层完全不用知情:

const ThemeContext = createContext("light");

function ThemeButton() {
  const theme = useContext(ThemeContext); // 直接读,不用 props
  return <button className={theme}>切换主题</button>;
}

function App() {
  const [theme, setTheme] = useState("light");
  return (
    <ThemeContext.Provider value={theme}>
      <Settings />   {/* 内部任何深度的组件都能 useContext 读到 theme */}
    </ThemeContext.Provider>
  );
}

两条使用纪律:

  • Context 是「环境」,props 是「参数」。会随每次使用变化的、需要调用方显式给的值(一条数据、一个回调)走 props;整个子树共享的环境值(主题、当前用户、语言)走 Context。把课程列表这种「页面数据」塞进 Context,等于让所有组件都依赖它,复用和测试都会变难。
  • Provider 有作用范围。useContext 读的是组件树上最近的上层 Provider——你可以在子树的某个分支再套一层 Provider,覆盖全局值。实验区里的局部深色面板就是这么实现的。

还要注意范围不要贪大:Provider 包得越大,value 变化时重渲染的面就越大。主题这类低频值问题不大;高频变化的值(输入框内容、鼠标位置)别放 Context。

示例拆解

实验区包含一个全局主题和一个局部覆盖:

  • 顶部按钮切换全局 theme(light / dark),普通面板跟随变化——它 useContext 读到的是最外层 Provider;
  • 「局部面板」外面套了第二层 <ThemeContext.Provider value="dark">,所以无论全局怎么切,它始终是深色——最近 Provider 优先;
  • 对照点:面板标题、边框、背景全部来自 context 值,没有任何 theme prop 出现在组件签名里;
  • 验证方式:连续切换全局主题两次,观察普通面板变色、局部面板纹丝不动,确认两条读取路径互不干扰。

动手任务

打开本课实验区,完成三个小改动:

  1. 给主题加第三档 sepia(米黄色背景),让全局按钮在 light / dark / sepia 间循环。
  2. 新增一个「局部浅色」面板:在全局 dark 时用内层 Provider 强制它保持 light。
  3. 把 ThemeContext 抽到单独文件(组件外 export const ThemeContext = createContext(...)),体验跨文件共享 context 的组织方式。

常见错误

  • 在组件内部创建 context:每次渲染都会得到一个新 context 对象,其他组件 useContext 永远读不到值;createContext 必须在模块顶层调用。
  • 忘包 Provider 直接 useContext:拿到的是 createContext 的默认值(比如 "light"),而且没有任何报错提醒你;要么提供 Provider,要么把默认值设计成有意义的兜底。
  • 把高频状态塞进全局 Context:输入框每敲一个字全应用重渲染;这类值留在最近的组件里,或拆分 Provider 范围。
  • 该用 props 的地方用了 Context:一个只被两处使用的回调绕道全局传递,调用关系反而看不清了;Context 解决的是「深层 + 广泛」的传递,不是所有传递。

检查与复盘

完成下方检查题确认理解。自查清单:

  • 能说出 props 和 Context 的分工标准:显式参数 vs 子树环境
  • 知道 useContext 读的是最近的上层 Provider,会用嵌套 Provider 做局部覆盖
  • 会评估一个 Context 的 value 变化频率,决定要不要缩小 Provider 范围

参考资料

动手实验
动手实验
预览
点击「运行」在隔离沙箱中预览
检查题共 2 题,提交后立即显示解析
答案由服务端判定;答错的题会自动进入复习计划。
Q1单选简单

关于 Context 的基本机制,正确的说法是?

Q2故障定位较难

bug 现场:App 用 ThemeContext.Provider 包住整棵树,value 直接写对象字面量。点击页面上「计数 +1」按钮(一个与主题完全无关的 state)后,整个页面所有组件都重渲染,2000 行的大列表明显卡顿。原因和修法是?

function App() {
  const [theme, setTheme] = useState('light');
  const [count, setCount] = useState(0);

  return (
    <ThemeContext.Provider value={{ theme, setTheme }}>
      <button onClick={() => setCount(count + 1)}>{count}</button>
      {/* BigList 内部 useContext(ThemeContext),渲染 2000 行,与计数无关 */}
      <BigList />
    </ThemeContext.Provider>
  );
}

读完这一课了?

标记完成后,工作台会安排接下来的学习。

Command Palette

Search for a command to run...