官方文档叫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的双向绑定和计算属性会很容易完成,新入门可能还不能很好的体会到这样做的优缺点,作为入门,我们可以多看看代码,先熟悉下这种感觉,之后实际项目中也许某些时候会给你一些灵感和理解吧~