原文连接:reactjs.org/docs/liftin…html
不少状况下咱们使用的多个组件须要对同一个数据作出对应的反应。在这里咱们推荐把这个共享的状态提高到距离这些组件最近的祖先组件。如今让咱们来看看这是怎么工做的。react
在本章中,咱们将会建立一个温度计算器来计算在给定温度下水是否会沸腾。git
首先咱们现建立一个BoilingVerdict
组件。它接收一个celsius
(摄氏度)做为prop
,并在页面上打印出在这个温度下水是否会沸腾。github
function BoilingVerdict(props) {
if (props.celsius >= 100) {
return <p>The water would boil.</p>;
}
return <p>The water would not boil.</p>;
}
复制代码
接下来,咱们将会建立一个Calculator
组件。它渲染了一个输入框来输入温度并将输入值绑定到this.state.temperature
。bash
除此以外,它也为当前输入的温度渲染相应的BoilingVerdict
。函数
class Calculator extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.state = {temperature: ''};
}
handleChange(e) {
this.setState({temperature: e.target.value});
}
render() {
const temperature = this.state.temperature;
return (
<fieldset>
<legend>Enter temperature in Celsius:</legend>
<input
value={temperature}
onChange={this.handleChange} />
<BoilingVerdict
celsius={parseFloat(temperature)} />
</fieldset>
);
}
}
复制代码
如今咱们有一个新需求,除了输入摄氏温度以外,咱们还须要一个可以输入华氏温度的输入框,而且这两个输入框是同步的。ui
首先咱们能够从Calculator
组件中提取出一个TemperatureInput
组件。并添加一个scale
做为prop来表明摄氏度(c)或华氏温度(f)。this
const scaleNames = {
c: 'Celsius',
f: 'Fahrenheit'
};
class TemperatureInput extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.state = {temperature: ''};
}
handleChange(e) {
this.setState({temperature: e.target.value});
}
render() {
const temperature = this.state.temperature;
const scale = this.props.scale;
return (
<fieldset>
<legend>Enter temperature in {scaleNames[scale]}:</legend>
<input value={temperature}
onChange={this.handleChange} />
</fieldset>
);
}
}
复制代码
如今咱们能够更改Calculator
组件来渲染两个不一样的温度输入。spa
class Calculator extends React.Component {
render() {
return (
<div>
<TemperatureInput scale="c" />
<TemperatureInput scale="f" />
</div>
);
}
}
复制代码
如今咱们有了两个输入框,可是当你在其中一个输入时另外一个输入框内的数据并不会更新。这就与咱们想要这两个输入框同步的需求矛盾了。双向绑定
咱们也无法在Calculator
中展现BoilingVerdict
了,由于Calculator
组件没有办法获取到隐藏在TemperatureInput
组件里的温度。
为了解决上述的矛盾,咱们现编写可以相互转换摄氏度和华氏温度的两个函数。
function toCelsius(fahrenheit) {
return (fahrenheit - 32) * 5 / 9;
}
function toFahrenheit(celsius) {
return (celsius * 9 / 5) + 32;
}
复制代码
这两个函数用来转换数字,如今咱们来编写另外一个函数,这个函数讲temperature
字符串和转换函数做为参数并返回一个字符串。咱们使用这个函数根据其中一个输入框的数据来计算另外一个输入框的数据。
同时若是输入的温度无效那么就返回一个空字符串,对有效的温度返回精确到小数点后三位的数字。
function tryConvert(temperature, convert) {
const input = parseFloat(temperature);
if (Number.isNaN(input)) {
return '';
}
const output = convert(input);
const rounded = Math.round(output * 1000) / 1000;
return rounded.toString();
}
复制代码
好比,tryConvert('abc', toCelsius)
返回一个空字符串,tryConvert('10.22', toFahrenheit)
返回 '50.396'
。
目前,两个TemperatureInput组件都将输入值保存在各自的局部state中。
class TemperatureInput extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.state = {temperature: ''};
}
handleChange(e) {
this.setState({temperature: e.target.value});
}
render() {
const temperature = this.state.temperature;
// ...
复制代码
可是咱们想要这两个输入框可以同步数据,当咱们在摄氏度输入时,华氏温度可以同时显示通过转化后的温度,反之亦然。
在React中,共享状态的方法是将state转移到离须要共享组件最近的公共父组件中,这称为“状态提高
”。如今咱们将把TemperatureInput
组件中的state转移到Calculator
中。
若是Calculator包含了共享状态,那么它就是这两个温度输入组件的“数据源”,这能使两个输入组件的数据始终保持一致。 由于两个TemperatureInput
组件的props都是来自共同的父组件Calculator
的,因此他们在数据显示上可以保持同步。
如今咱们来逐步了解这是怎么完成的。
首先,咱们将TemperatureInput
组件中的this.state.temperture
替换成this.props.temperature
。固然this.props.temperature
是经过Calculator组件传递的。
render() {
// Before: const temperature = this.state.temperature;
const temperature = this.props.temperature;
// ...
复制代码
咱们知道props
是只读的,当咱们将温度存储在本地state时,咱们能够经过setState()
来修改它。可是如今温度是经过父组件传递进来的,那么TemperatureInput
组件就没有修改温度的权限了。
React中,一般的解决方案是将组件变为“受控”的。就像在DOM中<input>
接收value
和onChange
做为prop同样,咱们可让TemperatureInput
组件接收从Calculator
传递来的prop:temperature
和onTemperatureChange
。
如今,当TemperatureInput
想要更新温度时,只要调用this.props.onTemperatureChange
就好了:
handleChange(e) {
// Before: this.setState({temperature: e.target.value});
this.props.onTemperatureChange(e.target.value);
// ...
复制代码
提示 temperature和onTemperatureChange在这里没有特殊的含义,只是一个属性名称,是能够随意定义的,它也能够是更大众化的value和onChange。
onTermperatureChange
属性和temperature
属性都是由父组件Calculator
提供的。它会经过修改自身的state来处理数据的变化,以此来从新渲染两个输入框内的数据。咱们很快就能够看到Calculator
组件的实现细节。
在咱们深刻了解Calculaor
组件的实现以前,先让咱们来回顾一下TemperatureInput
组件作了哪些修改。咱们将移除了本地state,将本来经过读取this.state.temperature
获取温度替换成读取this.props.temperature
获取,将经过调用setState()
修改数据替换成调用this.props.onTemperatureChange()
修改数据,这两种都由Calculator
组件提供。
class TemperatureInput extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
}
handleChange(e) {
this.props.onTemperatureChange(e.target.value);
}
render() {
const temperature = this.props.temperature;
const scale = this.props.scale;
return (
<fieldset>
<legend>Enter temperature in {scaleNames[scale]}:</legend>
<input value={temperature}
onChange={this.handleChange} />
</fieldset>
);
}
}
复制代码
如今让咱们来看看Calculator组件。
咱们将输入框的temperature和scale存储在本地state中,这是咱们从input中提高出来的状态,来做为两个input的数据源。这就是咱们渲染两个input须要的最小数据集合。
举个例子,如今咱们在摄氏度输入框内输入37,那么Calculator组件的state就将会是这样:
{
temperature: '37',
scale: 'c'
}
复制代码
若是以后咱们在华氏温度输入框中输入212,Calculator的state就将变成:
{
temperature: '212',
scale: 'f'
}
复制代码
有人会说为何存储两个input的值?能够,但不必。只要存储最近修改的值和它所表明的scale就足够了。由于咱们能够根据当前的temperature和scale推算出另外一个input的值。
如今,输入框的值是同步的了,由于它们的值都是由同一状态计算出来的。
class Calculator extends React.Component {
constructor(props) {
super(props);
this.handleCelsiusChange = this.handleCelsiusChange.bind(this);
this.handleFahrenheitChange = this.handleFahrenheitChange.bind(this);
this.state = {temperature: '', scale: 'c'};
}
handleCelsiusChange(temperature) {
this.setState({scale: 'c', temperature});
}
handleFahrenheitChange(temperature) {
this.setState({scale: 'f', temperature});
}
render() {
const scale = this.state.scale;
const temperature = this.state.temperature;
const celsius = scale === 'f' ? tryConvert(temperature, toCelsius) : temperature;
const fahrenheit = scale === 'c' ? tryConvert(temperature, toFahrenheit) : temperature;
return (
<div>
<TemperatureInput
scale="c"
temperature={celsius}
onTemperatureChange={this.handleCelsiusChange} />
<TemperatureInput
scale="f"
temperature={fahrenheit}
onTemperatureChange={this.handleFahrenheitChange} />
<BoilingVerdict
celsius={parseFloat(celsius)} />
</div>
);
}
}
复制代码
如今,不管你在哪一个输入框输入,Calculator
组件的this.state.scale
和this.state.temperature
都会更新。其中一个输入框的输入按照原样取值,因而用户的输入就这样被保存了,而另外一个输入框的值就会根据用户的输入推算出来。
让咱们梳理如下当咱们在一个输入框输入时代码的运行逻辑:
<input>
上的onChange方法,在咱们的例子中,调用的时TemperatureInput
组件的handleChange
方法。TemperatureInput
组件中的handleChange
被调用时,它将最新的数据传递给this.props.onTemperatureChange()
。这里的props都由父组件Calculator
提供。Calculator
组件在摄氏度的TemperatureInput
组件上声明的onTemperatureChange
事件所调用的方法是handleCelsiusChange
,在华氏温度上的则是handleFahrenheitChange
。因此这两个函数的调用取决于咱们在哪一个输入框输入数据。Calculator
组件调用setState()
将最新输入的值和scale从新渲染。Calculator
组件的render()
方法将UI渲染在页面上。两个输入框的值都在此时根据当前的状态从新计算,温度的换算也在此时进行。TemperatureInput
组件的render()
方法,并根据由Calculator
定义的props
渲染在页面上。BoilingVerdict
组件的render()
方法,并根据传进来的摄氏度温度渲染数据。每一次更新都按照上述步骤进行,因此两个输入框数据能够保持同步。
在React应用中任何可变的数据都应该有惟一数据源。一般来讲,state一开始都是包含在须要根据它来渲染数据的组件中。可是若是有其余组件也想要使用这个state,那么就须要把它提高到距离这两个组件最近的公共父组件中。可是相比于保持不一样组件的数据同步,咱们更应该依靠的是自顶而下的数据流。
相较于双向绑定,状态提高须要编写更多的“样板”代码。但这样作的好处是咱们能够更好地定位和分离bug。由于state只存在于各自的组件中,因此bug出现范围就大大减小了。除此以外,咱们能够自由地实现任何逻辑来拒绝或修改用户输入。
若是某个数据能够根据state或者props推导出来,那么这个数据就不该该存储在state中。就像本章的实例代码中,咱们只存储最新输入的温度和它对应的scale,并不须要将华氏温度和摄氏温度都存储在state中。另外一个输入框的数据在调用render()方法时就能够根据当前的数据推算出来。这能让咱们清除用户输入或者在不损失用户输入精度的状况下在其余区域使用用户输入的值。
当页面出现错误时,你可使用React Developer Tools查看props而且在组件树上追溯源头直到找到对应的组件为止。这可以让你轻松地定位bug。