经常,我们会遇到多个组件使用同一个数据,那时候我们就需要消除掉每一个组件中的数据,而构造出一个公共的祖先组件来处理。
我将以一个温度测试组件来进行演示
BoilingVerdict
组件接受一个摄氏度,输出是否沸腾
function BoilingVerdict(props) {
if (props.celsius >= 100) {
return <p>The water would boil.</p>;
}
return <p>The water would not boil.</p>;
}
Calculator
组件用于接受一个input
的输入,同时将值一直保存在this.state.temperature
中,以此同时,它也输出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>
);
}
}
增加另外一个输入,之前是摄氏度,这里是华氏度,当然他们是同步的
如何实现?
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>
);
}
}
class Calculator extends React.Component {
render() {
return (
<div>
<TemperatureInput scale="c" />
<TemperatureInput scale="f" />
</div>
);
}
}
上述代码很明显是实现不了的,因为每一个组件都只控制了自己内部的元素没有事受外部影响,何来同步一说。
之前我就说过数据的单向性,只能父组件控制子组件,并且这个控制还是基于子组件的接口而定的,除此之外,外部所有的组件都无法直接与子组件相连接,无法互相处理。
那么唯一能够用到就是基于父组件了,既然无法直接相处理,那么我们就通过他们的公共父组件相连接,这里的公共父组件一般为最近公共祖先组件,所谓的公共祖先组件在算法上的理解就是二叉树两个节点之间的最近公共祖先节点。
那么我们的代码就可以变为如下:
//其中省略了部分代码,希望大家自己完成补充实现响应的功能
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>
);
}
}
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>
);
}
}
这其中this.props.onTemperatureChange
是从外部传来的,很明显我们在之前就知道props
是可以获取标签增加的属性的,我们可以通过如下的代码让父组件传递函数到子组件中。
<TemperatureInput
scale="c"
temperature={celsius}
onTemperatureChange={this.handleCelsiusChange} />
我们再看this.handleCelsiusChange
函数的内容,由于我们用bind
将这个函数绑定到了父组件,不受子组件上下文的影响,当我们将参数传到这个函数时,会改变父组件的state
状态的数据,导致父组件会重新渲染,从而改变子组件的属性。
这里需要强调一点的就是,每一个组件的状态都不受外界的影响,只能自己控制自己,外界是无法控制,就像是私有属性,只能类内部的函数才能改变,外界是无法直接改变的。
只有当自身组件的state
改变时才会引起React
进行render
函数进行重新渲染,反之则不会渲染。
这里简单说一下,上述实现同步代码的React
处理机制
React
会检测到DOM
元素的onchange
事件,然后进行响应,先找到了TemperatureInput
的handleChange
函数handleChange
函数调用了this.props.onTemperatureChange
,由于我们进行bind
绑定,React
就找到了父组件Calculator
执行
this.props.onTemperatureChange
函数时,改变了父组件的state
状态,即调用了this.setState()
React
检测到父组件Calculator
的状态变化,然后调用它的render
函数,然后render
函数又调用了TemperatureInput
,更新了视图
这种从上而下的设计方式非常有助于我们调试程序,通过React
一些浏览器插件
下一篇将讲
React
中的组合和继承