作者:重庆互联网小徐 发布时间:2023-03-21 浏览:10902 赞(812) 收藏 评论(0)
我们将从一个名为 BoilingVerdict
的组件开始,它接受 celsius
温度作为一个 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>
);
}}
添加第二个输入框
我们的新需求是,在已有摄氏温度输入框的基础上,我们提供华氏度的输入框,并保持两个输入框的数据同步。
我们先从 Calculator
组件中抽离出 TemperatureInput
组件,然后为其添加一个新的 scale
prop,它可以是 "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 toFahrenheit(celsius) {
return (celsius * 9 / 5) + 32;}
上述两个函数仅做数值转换。而我们将编写另一个函数,它接受字符串类型的 temperature
和转换函数作为参数并返回一个字符串。我们将使用它来依据一个输入框的值计算出另一个输入框的值。
当输入 temperature
的值无效时,函数返回空字符串,反之,则返回保留三位小数并四舍五入后的转换结果:
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();}
例如,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; // ...
然而,我们希望两个输入框内的数值彼此能够同步。当我们更新摄氏度输入框内的数值时,华氏度输入框内应当显示转换后的华氏温度,反之亦然。
在 React 中,将多个组件中需要共享的 state 向上移动到它们的最近共同父组件中,便可实现共享 state。这就是所谓的“状态提升”。接下来,我们将 TemperatureInput
组件中的 state 移动至 Calculator
组件中去。
如果 Calculator
组件拥有了共享的 state,它将成为两个温度输入框中当前温度的“数据源”。它能够使得两个温度输入框的数值彼此保持一致。由于两个 TemperatureInput
组件的 props 均来自共同的父组件 Calculator
,因此两个输入框中的内容将始终保持一致。
让我们看看这是如何一步一步实现的。
首先,我们将 TemperatureInput
组件中的 this.state.temperature
替换为 this.props.temperature
。现在,我们先假定 this.props.temperature
已经存在,尽管将来我们需要通过 Calculator
组件将其传入:
render() {
// Before: const temperature = this.state.temperature; const temperature = this.props.temperature; // ...
我们知道 props 是只读的。当 temperature
存在于 TemperatureInput
组件的 state 中时,组件调用 this.setState()
便可修改它。然而,temperature
是由父组件传入的 prop,TemperatureInput
组件便失去了对它的控制权。
在 React 中,这个问题通常是通过使用“受控组件”来解决的。与 DOM 中的 <input>
接受 value
和 onChange
一样,自定义的 TemperatureInput
组件接受 temperature
和 onTemperatureChange
这两个来自父组件 Calculator
的 props。
现在,当 TemperatureInput
组件想更新温度时,需调用 this.props.onTemperatureChange
来更新它:
handleChange(e) {
// Before: this.setState({temperature: e.target.value}); this.props.onTemperatureChange(e.target.value); // ...
注意:
自定义组件中的
temperature
和onTemperatureChange
这两个 prop 的命名没有任何特殊含义。我们可以给它们取其它任意的名字,例如,把它们命名为value
和onChange
就是一种习惯。
onTemperatureChange
的 prop 和 temperature
的 prop 一样,均由父组件 Calculator
提供。它通过修改父组件自身的内部 state 来处理数据的变化,进而使用新的数值重新渲染两个输入框。我们将很快看到修改后的 Calculator
组件效果。
在深入研究 Calculator
组件的变化之前,让我们回顾一下 TemperatureInput
组件的变化。我们移除组件自身的 state,通过使用 this.props.temperature
替代 this.state.temperature
来读取温度数据。当我们想要响应数据改变时,我们需要调用 Calculator
组件提供的 this.props.onTemperatureChange()
,而不再使用 this.setState()
。
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>
);
}}
现在,让我们把目光转向 Calculator
组件。
我们会把当前输入的 temperature
和 scale
保存在组件内部的 state 中。这个 state 就是从两个输入框组件中“提升”而来的,并且它将用作两个输入框组件的共同“数据源”。这是我们为了渲染两个输入框所需要的所有数据的最小表示。
例如,当我们在摄氏度输入框中键入 37 时,Calculator
组件中的 state 将会是:
{
temperature: '37',
scale: 'c'}
如果我们之后修改华氏度的输入框中的内容为 212 时,Calculator
组件中的 state 将会是:
{
temperature: '212',
scale: 'f'}
我们可以存储两个输入框中的值,但这并不是必要的。我们只需要存储最近修改的温度及其计量单位即可,根据当前的 temperature
和 scale
就可以计算出另一个输入框的值。
由于两个输入框中的数值由同一个 state 计算而来,因此它们始终保持同步:
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>
);
}}
现在无论你编辑哪个输入框中的内容,Calculator
组件中的 this.state.temperature
和 this.state.scale
均会被更新。其中一个输入框保留用户的输入并取值,另一个输入框始终基于这个值显示转换后的结果。
欢迎广大用户为此页面进行评价,评价成功将获得积分奖励!