Kodokon kodokon.com

状态提升与数据共享

把每一份状态放在最近的公共祖先中,从而在多个组件之间共享数据而不重复它。

7 分钟 · 3 题

在 Kodokon 中打开本课

两个兄弟组件需要显示同一份数据:例如一个搜索框和它所过滤的列表。在各自内部使用局部状态是行不通的,因为它们彼此看不见。解决办法是把状态提升到它们最近的公共祖先中:由父组件持有数据,再通过 props 把它传回下去。

JSX
import { useState } from "react";

function ProductPage({ products }) {
  const [query, setQuery] = useState("");

  return (
    <>
      <SearchBar query={query} onChange={setQuery} />
      <ProductList products={products} query={query} />
    </>
  );
}
父组件持有 query,并把它共享给两个子组件。

于是子组件就成为了受控组件:它们以只读方式接收该值,若要修改它,就调用父组件提供的回调函数。数据通过 props 向下流动,变更请求通过函数向上流动。这种单向流动让应用的行为易于追踪。

JSX
function SearchBar({ query, onChange }) {
  return (
    <input
      value={query}
      onChange={(e) => onChange(e.target.value)}
      placeholder="Search for a product"
    />
  );
}

function ProductList({ products, query }) {
  const visible = products.filter((p) =>
    p.name.toLowerCase().includes(query.toLowerCase())
  );
  return (
    <ul>
      {visible.map((p) => <li key={p.id}>{p.name}</li>)}
    </ul>
  );
}
SearchBar 请求变更,ProductList 反映变更。

每一份数据都必须有唯一的数据来源。如果两个组件各自存储同一份状态的副本,这些副本迟早会不一致:一个被更新了,另一个没有,界面就变得前后矛盾。状态提升消除了这种风险。当数据需要跨越很多层时,组合(下一课)以及随后的 context 会减轻 props 的层层传递。

知识检测

确认你已牢记本课的重点内容。

  1. 两个兄弟组件都需要的状态应该放在哪里?
    • 放在两个兄弟中的第一个里,由它传给另一个
    • 放在它们最近的公共祖先中
    • 放在 React 之外声明的一个全局变量里
    • 放在两个兄弟各自里,通过手动同步
  2. 一个子组件如何修改由其父组件持有的状态?
    • 直接修改接收到的 prop
    • 调用父组件以 prop 形式传下来的回调函数
    • 使用 useState 并采用与父组件相同的初始值
  3. 为什么要避免把同一份数据存储在两份不同的状态里?
    • 它占用太多内存
    • React 禁止两个 useState 调用使用相同的初始值
    • 两份副本最终会不同步,界面变得前后矛盾