React起手-提炼状态
官方文档叫Lifting State up,不知道我翻译的是否妥当,哈哈哈。
组件之间的数据有些肯定是共享的,也就是说这个共享数据不管在什么地方被改变了,所有组件都要及时的反应到自身上,官方推荐提炼共有状态到他们最近的祖先上。举个例子来理解这个东东,例如有个温度计,当温度到100以上时候水开,反之不开。
function BoilingVerdict(props) {
if (props.celsius >= 100) {
return <p>The water would boil.</p>;
}
return <p>The water would not boil.</p>;
}
然后我们添加一个计算器来输入温度
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呢?一个输入的是摄氏度,一个输入的是华氏度。
首先明确下,第一步,我们接下来需要一个计算器组件来显示显示两个input组件:
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>
);
}
}
class Calculator extends React.Component {
render() {
return (
<div>
<TemperatureInput scale="c" />
<TemperatureInput scale="f" />
</div>
);
}
}
可以看到两个TemperatureInput组件是相互独立的,也就是说他们各自保有自己的==this.state = {value: ''};==所以输入其中一个另一个并不会相应的更新,其实我们不仅要让他们显示的时候互相更新,而且还需换算后更新。
有什么办法能让两个TemperatureInput的状态取得联系呢?没错,就是提炼共有状态到他们最近的父组件即可。在此之前我们先准备好换算函数tryConvert:
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();
}
接下来我们重写TemperatureInput:
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});
this.props.onChange(e.target.value);
}
render() {
// 干掉 const value = this.state.value;
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>
);
}
}
简单讲就是把私有状态的操作变成组件属性的操作。接下来就是构建其父组件,并且把共享状态放在这个地方,并通过属性传递给子组件使用:
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>
);
}
}
从上面我们可以看到如果变量可以从另一个变量中派生出来的时候,我们就不要把这些变量设为state,例如celsiusValue和fahrenheitValue可以通过value 和 scale进行计算,所以就不用提炼出来,应该在render方法里直接计算得到即可。
初步看起来有些复杂,但这也是React所提倡的从上至下单向数据流动的体现,其实这个需求用vue的双向绑定和计算属性会很容易完成,新入门可能还不能很好的体会到这样做的优缺点,作为入门,我们可以多看看代码,先熟悉下这种感觉,之后实际项目中也许某些时候会给你一些灵感和理解吧~