此文章是翻译lifting-state-up这篇React(版本v15.4.0)官方文档。
Lifting State Up
通常,几个组件需要响应相同的数据变化。我们建议提升共享状态(lifting the shared state up)到距离它们最近的父组件。让我们看看这是如何运转的。
在这部分,我们将要创建一个温度计算器来计算给定的温度是否使水沸腾。
我们将创建一个BoilingVerdict
组件。它接受一个celsius
温度作为props,然后输出是否能够使水沸腾:
1 | function BoilingVerdict(props){ |
然后,我们会创建一个Calculator
组件。它渲染一个<input>
接受你的键入的温度( temperature),并将此值保存在this.state.value
中。
另外,它会当前输入值渲染BoilingVerdict
。
1 | class Calculator extends Component { |
Adding a Second Input
我们的新需求是,除了Celsius input ,我们还需要提供一个Fahrenheit input,并且让它们保持同步(in sync)。
开始我们可以从Calculator
组件中提取一个TemperatureInput
组件。我们将添加一个scale
prop ,只可以接受“c”或“f”:
1 | const scaleNames = { |
现在我们可以修改Calculator
去渲染两个不同的temperature inputs:
1 | class Calculator extends Component { |
现在我们有两个input,但是当你在其中一个键入temperature时,另一个不会更新。这同我们的需求相反:我们想要它们保持同步(in sync)。
我们也不能在Calculator
中展示BoilingVerdict
。这个Calculator
不知道当前的temperature 因为它被隐藏在TemperatureInput
中。
Lifting State Up
首先,我们要写两个函数来互相转换Celsisus
和 Fahrenheit
。
1 | function toCelsius(fahrenheit){ |
这两个函数转换数值。我们还要写另一个函数,它接受一个string 类型的value
和一个转换函数作为参数并且返回一个string。我们调用此函数来通过一个输入值获取另一个输入值。
如果是一个无效的value
它将返回一个空字符串,并且输出值保留三位小数。
1 | function tryCovert(value, convert){ |
例如,tryConvert('abc', toCelsisu)
将返回一个空字符串,而tryConvert('10.22', toFahrenheit)
将返回50.369
。
下面,我们将从TemperatureInput
中移除state。
相反,它将会从props 中接受value
和onChange
句柄:
1 | class TemperatureInput extends Component { |
如果几个组件需要访问相同的数据,一个标志就是state 应该被提升到它们相邻的最近父组件。在我们这个例子中,这个父组件是Calculator
。我们将要存储当前的value
和scale
在他的state 中。
实践证明,我们不需要存储两个inputs 值。我们只需要存储最近修改的input ,和它所代表的scale 就可以了。我们可以根据当前的value
和scale
推出另一个input 中的值。
因为是根据相同的state 来计算它们的值,所以inputs 可以保持同步。
1 | class Calculator extends Component { |
现在,无论是你在那一个input 进行编辑,在Calculator
中this.state.value
和this.state.scale
都会更新。其中的一个input 获取value,任何input 都会被保存,另一个input 都会基于这个值来更新。
Lessions Learned
在React 应用中任何数据变动都应该是基于一个”source of truth”。通常state 被首先加入组件去渲染。然后,如果另一个组件也需要它,那么你就可以将它们提升到它们的父组件中。而不是尝试在不同的组件中同步state,你应该依赖top-down data flow。
通过专注写模版代码(boilerplater)而不是进行双向绑定,但是有一个优点,它能花费很少工作去发现和隔离bug。因为任何state 都存在于组件而组件可以独立改变state,确定bug 的返回也会减少。除此之外,你可以实现自定义逻辑去拒绝或改变用户输入。
如果有些东西可以来自props 或者state ,那么它就不应该存在与state 中。例如,我们存储value
和scale
而不是存储celsiusValue
和fahrenheitValue
。另一个输入值总是可以在render()
方法中被计算得到。这可以让我们清晰的 应用在任何取值范围而不会失去用户输入精度。
如果在UI 中有错误,你可以使用React Developer Tools 对props 进行检测,并将其移动到树上,知道找到负责更新的组件为止。这可以让你从源代码上跟踪bug。