Skip to content
This page has been auto-translated and may contain errors.View in English

状态提升

文本输入框旁边有一个预览框,需要显示输入框中的内容。它们都在同一个组件树里,但一个组件内声明的状态对它的兄弟组件是不可见的,所以输入框和预览框谁都看不到对方的数据,也改不了。解决办法就是把状态移到它们最近的公共父组件,然后以 props 的形式传递给两个子组件。这就叫状态提升。

下面是一对共享一个值的组件:

jsx
import { useState } from 'react'

function Form() {
  const [text, setText] = useState('')

  return (
    <>
      <TextInput text={text} onChange={setText} />
      <TextDisplay text={text} />
    </>
  )
}

function TextInput({ text, onChange }) {
  return <input value={text} onChange={e => onChange(e.target.value)} />
}

function TextDisplay({ text }) {
  return <p>你输入了: {text}</p>
}

FormTextInputTextDisplay 最近的公共父组件,所以 text 就住在这里。它把 text 传给两个子组件,还把 setText 作为 onChange prop 传给 TextInput。用户输入时,TextInput 调用 onChange 传入新值,Form 的状态更新,Form 重新渲染。这次重新渲染把新的 text 传回两个子组件,所以 TextDisplay 显示的就是输入框里的内容,自己不用管理任何状态。

让状态尽可能靠下

状态提升不是随时都要做的,只在真正需要时才用。基本原则就是状态共同位置:让每个状态尽可能地靠下放在组件树里,离真正使用它的组件近一点,只有在其他地方也需要读或改它时才向上提升。如果一个组件的状态没有其他组件触碰,就让它自己管理。

这很重要,因为提升是有代价的。每向上提一层,父组件就得把它作为 prop 传回下去,中间的每个组件都得接收并转发这个 prop,即便它们根本用不上这个值。先从局部开始。状态只向上提到真正需要共享它的最近组件,不要再往上了。

把修改函数传下去

注意 Form 不仅传了 text 下去,还传了 setText 下去,作为 onChange prop。这是状态提升的另一半:拥有状态的组件通过 props 提供一个修改状态的方式,子组件调用这个函数而不是自己管理值。TextInput 根本看不到 setText。它只是调用被传进来的那个函数,让父组件决定这个函数具体做什么。

对于嵌套很深的不同组件之间共享的状态,一层层传 props 会变得不实用。React 有针对这种情况的工具,Context 就是其中之一,Hooks 章节介绍的 useContext 可以在不逐层传递的情况下读取某个值。那超出本章范围;上面的模式是你首先应该用的。

选一个组件来管理某个特定的状态,把它当作这个值的唯一数据源。别让第二个组件把同样的数据存一份副本,然后手动试图保持两份同步。两份数据在第一次同步遗漏的时候就会分叉,追踪这种 bug 比多传几个 props 来提升状态还要烦。如果一个值能从已有的状态推导出来,那就在渲染时推导它,别另外存一份。

要注意的信号是 prop drilling:一个 prop 为了到达某个深层消费组件而逐层传递,中间每个组件都只是直通传递,从不使用它。一两层这样是正常的,不值得重构。当它跨越很多层,或者树的多个无关分支都需要同一个值时,Context 或状态管理库才真正派上用场。对于靠得比较近的组件间共享的状态,状态提升加 props 传递还是最合适的工具;一旦组件之间的树变得很深,就不适用了。

Juno把共享状态移到父组件 两个组件需要同一个值时,窍门就是把这个值给它们最近的公共父组件,而不是给其中任何一个。父组件持有状态,把值传给两个子组件,还传下去一个函数让子组件可以申请修改。子组件不拥有这个值;它用被传进来的东西,并向父组件申请更新。
Juno把共享状态移到父组件 两个组件需要共享状态时,把状态提升到最近的公共父组件,以 props 形式传下去值和它的修改函数,通常是一个值 prop 和一个 onChange 风格的回调。除此之外保持状态尽可能本地化;只在其他地方也需要时才提升。对于跨越很多嵌套组件共享的东西,Context 更合适,Hooks 章节会讲怎么用。
Juno把共享状态移到父组件 默认用状态共同位置,只提升到最近的真正需要共享的组件,以 props 传下值和修改函数。每个值只有一个数据源,别试图在多个组件间同步副本。注意 prop drilling 这个信号——某个 prop 在不使用它的层里逐层传递——这时 Context 或状态库比继续提升更合适。

接下来:副作用,你会用 useEffect 来处理渲染之外发生的工作。