重庆互联网,19年企业互联网解决经验,专业网站开发、移动端开发、微信端开发、小程序开发!
重庆网络推广公司

React教程十二:状态提升

作者:重庆互联网小徐 发布时间:2023-03-21 浏览:10591 赞(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 均会被更新。其中一个输入框保留用户的输入并取值,另一个输入框始终基于这个值显示转换后的结果。


网友留言评论
我要评论
评论

欢迎广大用户为此页面进行评价,评价成功将获得积分奖励!

  • 赞(0
    踩(0
重庆网站定制建设
  • 重庆网站建设平台
  • 重庆网站优化公司
  • 重庆网络推广公司哪家好
  • 重庆APP制作公司
版权所有 ©2004-2024 重庆市渝中区圣灵科技信息有限公司 渝ICP备16004600号-14 渝公网安备50010802001420号 电子营业执照
重庆公众号制作哪家好
二维码
联系客服 重庆商城网站建设