通常,一些组件需要对同一个数据做出反应,这时我们建议将这些组件中关于这个数据的State提升至和它们距离最近的父级组件中。让我看看该如何做到这一点:
在这个章节,我们将会创建一个温度计算器,根据用户给定数值来计算水是否沸腾。
我们首先创建一个BoilingVerdict的组件,他接受celsius作为它的props,然后打印出水是否沸腾:
function BoilingVerdict(props) {
if (props.celsius >= 100) {
return <p>The water would boil.</p>;
}
return <p>The water would not boil.</p>;
}
接下来,我们创建Calculator组件,它包含一个input控件,并将用户输入input控件的值保存在this.state.value中,它依然包含上述中的BoilingVerdict组件,用来根据用户输入来反映水是否沸腾:
class Calculator extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.state = {value: ''};
}
handleChange(e) {
this.setState({value: e.target.value});
}
render() {
const value = this.state.value;
return (
<fieldset>
<legend>Enter temperature in Celsius:</legend>
<input
value={value}
onChange={this.handleChange} />
<BoilingVerdict
celsius={parseFloat(value)} />
</fieldset>
);
}
}
在添加一个input
我们新的要求是这样的,现在需要另一个Fahrenheit温度的输入框,它和上述Celsius的输入框内的值保持同步。
首先我们可以将TemperatureInput组件从Calculator组件中抽象出来,我们添加一个scale的prop用来区分输入框是Celsius还是Fahrenheit:
const scaleNames = {
c: 'Celsius',
f: 'Fahrenheit'
};
class TemperatureInput extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.state = {value: ''};
}
handleChange(e) {
this.setState({value: e.target.value});
}
render() {
const value = this.state.value;
const scale = this.props.scale;
return (
<fieldset>
<legend>Enter temperature in {scaleNames[scale]}:</legend>
<input value={value}
onChange={this.handleChange} />
</fieldset>
);
}
}
现在我们的Calculator输入框可以渲染两个不同的输入框了:
class Calculator extends React.Component {
render() {
return (
<div>
<TemperatureInput scale="c" />
<TemperatureInput scale="f" />
</div>
);
}
}
现在我们有两个输入框了,但是当我们给其中一个输入框输入内容时,另一个输入框的内容不会同步改变,这不符合我们我们的需求。由于当前的温度值被隐藏在TemperatureInput组件的内部,所以Calculator组件无法知道当前的温度值,BoilingVerdict组件也就无法知道知道水是否沸腾。
State提升
首先我们需要两个函数,它们分别将fahrenheit转为Celsius,将Celsius转为ahrenheit:
function toCelsius(fahrenheit) {
return (fahrenheit - 32) * 5 / 9;
}
function toFahrenheit(celsius) {
return (celsius * 9 / 5) + 32;
}
上面的两个函数都起到转换数值的作用。我们在另外写一个函数,这个函数接受一个字符串和一个函数作为参数,返回值为一个字符串。当输入一个非数字的值时,它返回一个空字符串:
function tryConvert(value, convert) {
const input = parseFloat(value);
if (Number.isNaN(input)) {
return '';
}
const output = convert(input);
const rounded = Math.round(output * 1000) / 1000;
return rounded.toString();
}
For example, tryConvert('abc', toCelsius) returns an empty string, and tryConvert('10.22', toFahrenheit) returns '50.396'.
Next, we will remove the state from TemperatureInput.
Instead, it will receive both value and the onChange handler by props:
class TemperatureInput extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
}
handleChange(e) {
this.props.onChange(e.target.value);
}
render() {
const value = this.props.value;
const scale = this.props.scale;
return (
<fieldset>
<legend>Enter temperature in {scaleNames[scale]}:</legend>
<input value={value}
onChange={this.handleChange} />
</fieldset>
);
}
}
如果一些组件需要用相同的state时,只需要把这个state提升至最近的父组件内就好。在我们的例子中,这个组件便是Calculator,我们把value和scale储存到它里面。
我们可以把两个输入框的值都储存起来,但是这种做法没有必要。我们只要储存最近一次的数值和scale就可以了,因为我们可以根据value 和scale来计算另一个scale的value:
class Calculator extends React.Component {
constructor(props) {
super(props);
this.handleCelsiusChange = this.handleCelsiusChange.bind(this);
this.handleFahrenheitChange = this.handleFahrenheitChange.bind(this);
this.state = {value: '', scale: 'c'};
}
handleCelsiusChange(value) {
this.setState({scale: 'c', value});
}
handleFahrenheitChange(value) {
this.setState({scale: 'f', value});
}
render() {
const scale = this.state.scale;
const value = this.state.value;
const celsius = scale === 'f' ? tryConvert(value, toCelsius) : value;
const fahrenheit = scale === 'c' ? tryConvert(value, toFahrenheit) : value;
return (
<div>
<TemperatureInput
scale="c"
value={celsius}
onChange={this.handleCelsiusChange} />
<TemperatureInput
scale="f"
value={fahrenheit}
onChange={this.handleFahrenheitChange} />
<BoilingVerdict
celsius={parseFloat(celsius)} />
</div>
);
}
}
现在,无论你在哪一个输入框输入数值,Calculator组件中的this.state.value值和this.state.scale值都会被更新,这样另一个没被输入数值的输入框的值也会相应的被改变。