- 通常,我们需要几个不同的组件来显示相同的数据变化。我们建议提升state到离它们最近的父级组件中。让我们来看看它是怎么执行的。
- 在这一小节中,我们会创建一个根据给定温度判断水是否能被烧开的温度计算器。
- 我们会以一个叫 BoilingVerdict 的组件开始。它接收摄氏温度并作为prop属性,然后显示它是否足够烧开水:
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>
);
}
}
增加第二个表单
- 我们有一个信需求,除了Celsius表单外,我们提供一个华氏度的表单,并且保持他们同步。
- 我们可以从 Calculatro 组件中提取一个 TemperatureInput 组件。我们会为它增加一个 scale属性,其值可以为c或f。
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>
);
}
}
- 我们现在可以改变 Calculator 组件,来渲染两个独立的温度表单:
class Calculator extends React.Component {
render() {
return (
<div>
<TemperatureInput scale="c" />
<TemperatureInput scale="f" />
</div>
);
}
}
- 现在我们有两个表单了,但是当你在其中一个温度表单中填写数值,另一个表单不会更新。这与我们的需求相矛盾:我们想保持他们同步。
- 我们也不能展现 Calculator 组件中的 BoilingVerdict 组件。 Calculator 组件不知道当前的温度,因为温度隐藏在 TemperatureInput 组件里面。
编写转换函数
- 首先,我们写两个函数,来让摄氏度与华氏度互相转换。
function toCelsius(fahrenheit) {
return (fahrenheit - 32) * 5 / 9;
}
function toFahrenheigt(celsius) {
return (celsius * 9 / 5) + 32;
}
- 这两个函数会转换两种温度。我们会写另一个函数来接受temperature字符串和一个转换函数作为参数,然后返回一个字符串。我们会用它基于另一个表单来计算数值。
- 当temperature非法时,它返回一个空字符串,并且它保持小数点后三位。
function tryConvert(temperaure, convert) {
const input = parseFloat(temperature);
if (Number.isNaN(input)) {
return '';
}
const output = convert(input);
const rounded = Math.round(output * 1000) / 1000;
return rounded.toString();
}
- 例如,tryConvert(‘abc’, toCelsius)返回一个空字符串,tryConvert(‘10.22’, toFahrenheit)返回’50.396’。
提升状态
现在,两个 TemperatureInput 组件分别持有他们自己的state:
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;
}
}
- 然而,我们想让这两个表单保持同步。当我们更新Celsius表单时,Fahrenheit表单应该呈现转换后的温度,反之亦然。
- 在React中,分享状态是通过移动状态到理他们最近的父级组件来完成的。这叫做“状态机提升”。我们会把当前状态从TemperatureInput组件移动到Calculator组件中。
- 如果Calculator组件拥有共享状态,它就成为当前两个温度表单的”真实来源”。这回让他们都具有值,并且一致。既然两个TemperatureInput组件的静态属性props都来自于相同的父级组件Calculator,这两个表单将会保持同步。
- 让我们看看如何一步步完成这个工作。
- 首先,在 TemperatureInput 组件中,我们用 this.props.temperature 替换 this.state.temperature。尽管它将从Calculator组件传来,现在让我们假设 this.props.temperature 已经存在:
render() {
// Before: const temperature = this.state.temperature;
const temperature = this.props.temperature;
}
我们知道props属性是只读的。当temperature之前是本地状态时,TemperatureInput组件可以调用this.setState()来改变它。但是,现在temperature是来自于父级的props,TemperatureInput无权控制它。
- 在React中,通常用”受控”组件来解决这个问题。就想input组件同时接受value和onChange属性,自定义的TemperatureInput也可以从它的父级组件Claculator组件同时接收temperature和onTemperatureChange属性。
- 现在,当TemperatureInput组件想要更新它的温度时,它调用 this.props.onTemperatureChange :
handleChange(e) {
// Before: this.setSate({temperature: e.target.value});
this.props.onTemperatureChange(e.target.value);
}