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 传给两个子组件,还把 setTextonChange 的名字传给 TextInput。当用户输入时,TextInput 调用 onChange 来传递新值,Form 的状态更新,然后重新渲染。这次重新渲染会把新的 text 传回给两个子组件,这样 TextDisplay 就能显示输入框里的内容,而自己不需要管理任何状态。

让状态尽可能靠近使用它的地方

状态提升只在需要的时候才做。指导原则就是状态共置:每个状态都应该尽可能靠近实际使用它的组件,只有在其他组件也需要读取或修改它时才往上移。一个组件如果自己的状态没有其他地方用到,就应该把它留在自己这里。

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

把 setter 函数传下去

注意 Form 不仅传下去了 text,还传下去了 setText,作为 onChange prop。这是状态提升的另一半:拥有状态的组件通过 props 提供一个改变它的办法,子组件调用这个函数而不是自己管理这个值。TextInput 永远看不到 setText 本身。它调用给它的任何函数,由父组件决定这个函数实际做什么。

对于嵌套很深的组件之间共享的状态,通过每一层逐个传递 props 就不太实用了。React 有工具可以处理这种情况,比如 Context,Context 这一章讲解了 useContext,这个 hook 可以直接读取一个值而不用每层组件都转发。那超出了这章的范围;上面讲的这种方式是首先应该用的。

让一个组件拥有某个特定的状态,把它当作这个值的唯一数据源。别让第二个组件在自己的状态里保存同样数据的副本,然后手动尝试保持两者同步。一旦错过一个更新路径,两份数据就会漂移,追踪这个 bug 比直接提升状态多用的那几个 props 麻烦得多。如果一个值能从已有的状态推导出来,就在渲染时计算它,而不是单独存储。

需要留意的信号是 prop drilling:一个 props 被逐层传递下去纯粹是为了到达某个深层的消费者,中间每个组件都只是转发它而从不使用。一两层这样的情况很正常,不值得重构。当它跨越很多层,或者树的几个不相关的分支都需要同一个值时,用 Context 或状态管理库就划算了。对于靠得比较近的组件之间共享的状态,状态提升和 props 传递仍然是对的工具;一旦树中间的距离变深了,它们就不是对的工具了。

Juno把共享状态移到父组件 当两个组件需要同一个值时,诀窍是把这个值给它们的最近公共父组件,而不是给任何一个子组件。这个父组件持有这个状态,把值传给两个子组件,还传下去一个函数让子组件可以请求更新。

子组件从不拥有这个值,它只使用给它的东西,然后请求父组件去更新它。

Juno把共享状态移到父组件 当两个组件需要共享一个值时,把状态提升到它们最近的公共父组件,然后作为 props 传下去,通常是一个 value prop 和一个 onChange 风格的回调。除此之外,让状态尽可能靠近使用它的地方;只在有其他东西也需要它时才提升。

对于被很多层嵌套的组件共享的东西,Context 是更好的工具,Context 这一章讲解了怎么用。

Juno把共享状态移到父组件 默认保持状态的共置,只提升到最近的共享消费者,把值和它的 setter 作为 props 传下去。每个值只有一个数据源,不要在多个组件间同步副本。

watch 出现 prop drilling 的情况,即 props 被逐层转发但中间的组件不使用它,这是信号表明 Context 或 store 是更好的工具,而不是继续提升。

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