状态提升
一个文本输入框旁边有一个预览区,需要显示输入框里的内容。它们在同一个组件树里,但声明在一个组件内部的状态对它的兄弟组件是不可见的,所以输入框和预览区都没法读取或改变彼此的值。解决办法就是把状态移到这两个组件都能访问的最近的公共父组件里,然后作为 props 传给它们。这就叫状态提升。
这里是一对共享一个值的组件:
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>
}Form 是 TextInput 和 TextDisplay 的最近公共父组件,所以 text 状态就放在它里面。它把 text 传给两个子组件,还把 setText 以 onChange 的名字传给 TextInput。当用户输入时,TextInput 调用 onChange 来传递新值,Form 的状态更新,然后重新渲染。这次重新渲染会把新的 text 传回给两个子组件,这样 TextDisplay 就能显示输入框里的内容,而自己不需要管理任何状态。
让状态尽可能靠近使用它的地方
状态提升只在需要的时候才做。指导原则就是状态共置:每个状态都应该尽可能靠近实际使用它的组件,只有在其他组件也需要读取或修改它时才往上移。一个组件如果自己的状态没有其他地方用到,就应该把它留在自己这里。
这很重要是因为提升是有代价的。每次把一个状态往上提一层,父组件就得把它作为 props 传回下去,中间的每个组件都得接收并转发这个 props,即便它们根本用不上这个值。从本地开始。只把状态提升到真正需要共享它的最近的组件,不要提升得比这更高。
把 setter 函数传下去
注意 Form 不仅传下去了 text,还传下去了 setText,作为 onChange prop。这是状态提升的另一半:拥有状态的组件通过 props 提供一个改变它的办法,子组件调用这个函数而不是自己管理这个值。TextInput 永远看不到 setText 本身。它调用给它的任何函数,由父组件决定这个函数实际做什么。
对于嵌套很深的组件之间共享的状态,通过每一层逐个传递 props 就不太实用了。React 有工具可以处理这种情况,比如 Context,Context 这一章讲解了 useContext,这个 hook 可以直接读取一个值而不用每层组件都转发。那超出了这章的范围;上面讲的这种方式是首先应该用的。
子组件从不拥有这个值,它只使用给它的东西,然后请求父组件去更新它。
接下来:副作用,你会用 useEffect 来处理渲染之外的工作。

