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

状态提升与共享

状态提升与共享 · 阅读后完成下方实验与检查题,再用「标记完成」记录进度。

学习目标

两个控件各管各的 state,数据就永远对不上。这一课讲状态提升:把多个组件共同依赖的状态搬到最近的公共父组件,让子组件变成「显示 props、上报变更」的受控形态。读完你能完成一次规范的提升改造,并说清判断「该不该提升」的依据。

问题场景

课程库页面有两个筛选控件和一个结果列表:搜索框自己存 query,难度下拉自己存 level,列表读这两个值做过滤。为了「互不影响」,每个控件都把自己的 state 藏在内部。然后需求来了:头部要显示「当前筛选出 N 门课」,底部要一个「清空筛选」按钮——它们都拿不到控件内部的 state。

核心解释

判断依据一句话:当一个状态被两个以上组件需要时,把它提升到它们的最近公共父组件。 state 留在组件内部的前提是「只有自己用」;一旦有人共享,私有 state 就成了数据孤岛。

提升分三步:

  1. 删掉子组件里的 state,换成两个 props:value(当前值)和 onChange(变更回调);
  2. 父组件持有 state,往下传 value,往上传 setXxx;
  3. 子组件只显示、只上报——它不再拥有数据,输入框读 props.value,输入时调 props.onChange。
function SearchBox({ value, onChange }) {
  return (
    <input
      value={value}
      onChange={(e) => onChange(e.target.value)}
      placeholder="搜索课程"
    />
  );
}

function CourseLibrary() {
  const [query, setQuery] = useState("");
  return <SearchBox value={query} onChange={setQuery} />;
}

这种「组件不持有状态、完全由 props 驱动」的形态叫受控组件。它的价值在实验里看得见:搜索框、下拉、结果计数读的是同一个 state,任何一处变化,其余同步更新;「清空筛选」只是父组件里一句 setQuery("")。下拉框同理:

function LevelSelect({ value, onChange }) {
  return (
    <select value={value} onChange={(e) => onChange(e.target.value)}>
      <option value="all">全部难度</option>
      <option value="core">核心</option>
    </select>
  );
}

反过来,提升也有代价:父组件成了状态枢纽,改一处要顺着 props 打通两层。所以不是提得越高越好——只提升「确实被共享」的状态,只有一个使用者的交互状态(比如输入框聚焦与否)留在内部。这与 R11 的最小状态集是同一件事的两个方向:值要少(不存派生),位置要准(只放公共父级)。

示例拆解

实验区是同一个课程库的两种结构,关键点如下:

  • 孤岛版:搜索框和难度下拉各自持有 state,列表自己再复制一份做过滤——输入「react」后,头部计数和列表对不上,因为它们读的是两份数据;
  • 提升版:App 持有 query 和 level,三个控件与列表全部受控;计数是 courses.filter(...).length 的派生值(R11 的规则继续生效);
  • 「清空筛选」按钮一行清两个 state,验证所有控件同步归零——孤岛版做不到;
  • 受控输入的实现细节:value + onChange 成对出现,父组件是唯一写入者。

验证方式:提升版里在搜索框打字,计数、下拉、列表、按钮全部同时反应;孤岛版里对比同一操作,至少有两处不同步。

动手任务

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

  1. 给提升版加一个「只看已完结」开关,把它也接入同一个状态源。
  2. 在孤岛版给列表和搜索框各加一次同步代码,体验维护两份数据的成本。
  3. 给「清空筛选」加一个条件:仅当有任何筛选生效时按钮才可点(用派生值判断)。

常见错误

  • 提升到过高的层级:状态挂在 App 顶层,整个组件树跟着它的每次变化重渲染;提升到「最近」公共父组件就够了。
  • 子组件偷偷留一份内部 state 当初始值:受控后又存 useState(props.value),props 更新它不跟,两份打架;受控组件不存副本。
  • 只传 value 忘传 onChange:输入框打不了字——没有 onChange 的受控输入是只读的;两个 props 成对出现。
  • 把每个子组件的 UI 细节状态也一并提升:悬停、聚焦这类只有自己用的状态留在内部,别把提升变成全局收容。

检查与复盘

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

  • 判断新状态的去处时先问「谁会用」:多人共享才提升,单人使用留内部
  • 受控组件的形态能默写:删内部 state,接 value + onChange
  • 验证共享状态用「一处改、处处变」的交互实测,不靠脑补

参考资料

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

页面里有两个温度输入框(摄氏/华氏各一个)和一个「是否沸腾」的结论展示。两个输入框的值需要互相换算联动。合理的 state 设计是?

读完这一课了?

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

Command Palette

Search for a command to run...