通常,几个组件拥有相同的数据,此时就需要将这个数据放到最近的公共祖先的状态里。
接下来,我们将创建一个温度计算器,并根据给定水温判断是否沸腾。最终效果如图所示:
也就是两个输入框分别对应摄氏温度和华氏温度,只要有一个输入框内容变化,另一个也会相应变化,并判断是否会沸腾。
我们先考虑只有摄氏温度输入框的情况,
class Calculator extends React.Component {
constructor(props) {
super(props);
this.state={
temperature: ''
};
this.handleChange = this.handleChange.bind(this);
}
handleChange(e) {
this.setState({temperature: e.target.value})
}
render() {
return (
<fieldset>
<legend>Enter temperature in celsius:</legend>
<input onChange={this.handleChange} />
<BoilingVerdict celsius={parseFloat(this.state.temperature)}>
</BoilingVerdict>
</fieldset>
)
}
}
function BoilingVerdict(props) {
if (props.celsius >= 100) {
return <p>The water would boil.</p>;
}
return <p>The water would not boil.</p>;
}
ReactDOM.render(
<Calculator />,
document.getElementById('root')
);
接着我们再考虑有两个输入框的情况,这时候应该把输入框单独作为一个组件提取出来:
class TemperatureInput extends React.Component {
constructor(props) {
super(props);
this.state={
temperature: ''
};
this.handleChange = this.handleChange.bind(this);
}
handleChange(e) {
this.setState({temperature: e.target.value})
}
render() {
return (
<fieldset>
<legend>Enter temperature in {this.props.scale}:</legend>
<input onChange={this.handleChange} />
<BoilingVerdict celsius={parseFloat(this.state.temperature)}>
</BoilingVerdict>
</fieldset>
)
}
}
const scaleName = {
'c': 'celsius',
'f': 'Fahrenheit'
}
class Calculator extends React.Component {
render() {
return (
<div>
<TemperatureInput scale={scaleName.c}/>
<TemperatureInput scale={scaleName.f}/>
</div>
)
}
}
function BoilingVerdict(props) {
if (props.celsius >= 100) {
return <p>The water would boil.</p>;
}
return <p>The water would not boil.</p>;
}
ReactDOM.render(
<Calculator />,
document.getElementById('root')
);
好,现在得到了两个输入框,可是这两个输入框是独立的,一个框的内容不会同步到另一个,由于以前使用vue的时候,我会想到非父子组件通信,也就是通过两个子组件的共同父组件来传递值,react这里也一样,遵循的原则是数据流top-down-flow,所以也不应该考虑一个组件传值给另一个组件,也应该采用公共组件的方式,那么这里我们就应该把我们想要的值放到公共组件Calculator里.
整个工作流程应该是这样的:两个组件都接受父组件的数据,组件一数据变化了,就去触发父组件的事件,这个被触发的事件就去更新父组件的状态,这一更新状态,react就要去重新render了,因为这时组件二的数据也随着变了,所以也就同步显示了。
class TemperatureInput extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
}
handleChange(e) {
this.props.onTemperatureChange(e.target.value)
}
render() {
return (
<fieldset>
<legend>Enter temperature in {this.props.scale}:</legend>
<input value={this.props.temperature} onChange={this.handleChange}/>
<BoilingVerdict celsius={this.props.temperature}>
</BoilingVerdict>
</fieldset>
)
}
}
const scaleName = {
'c': 'celsius',
'f': 'Fahrenheit'
}
class Calculator extends React.Component {
constructor(props) {
super(props);
this.state={
temperature: '',
scale: 'c'
};
this.handleCelsiusChange=this.handleCelsiusChange.bind(this);
this.handleFahrenheitChange=this.handleFahrenheitChange.bind(this);
}
handleCelsiusChange(temperature) {
this.setState({scale:'c', temperature});
}
handleFahrenheitChange(temperature) {
this.setState({scale:'f', temperature});
}
render() {
const temperature = this.state.temperature;
const scale = this.state.scale;
const celsius = scale === 'f' ? tryConvert(temperature, toCelsius) : temperature;
const fahrenheit = scale === 'c' ? tryConvert(temperature, toFahrenheit) : temperature;
return (
<div>
<TemperatureInput scale={scaleName.c} temperature={celsius} onTemperatureChange={this.handleCelsiusChange}/>
<TemperatureInput scale={scaleName.f} temperature={fahrenheit} onTemperatureChange={this.handleFahrenheitChange}/>
</div>
)
}
}
function BoilingVerdict(props) {
if (props.celsius >= 100) {
return <p>The water would boil.</p>;
}
return <p>The water would not boil.</p>;
}
function toCelsius(fahrenheit) {
return (fahrenheit - 32) * 5 / 9;
}
function toFahrenheit(celsius) {
return (celsius * 9 / 5) + 32;
}
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();
}
ReactDOM.render(
<Calculator />,
document.getElementById('root')
);