状态提升(精读React官方文档—10)

简介: 状态提升(精读React官方文档—10)

这是我参与更文挑战的第23天,活动详情查看:更文挑战

为什么需要状态提升?

有时候,多个组件需要共享状态,此时需要将共享状态提升到最近的共同父组件中去。

首先创建一个判断水是否沸腾的组件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>
    );
  }
}
复制代码

解读

我们希望的是当一个输入框的值发生变化的时候,另一个输入框的值也发生变化。即让他们保持同步。

编写转换函数

编写两个可以在摄氏度与华氏度之间相互转换的函数:

function toCelsius(fahrenheit) {
  return (fahrenheit - 32) * 5 / 9;
}
function toFahrenheit(celsius) {
  return (celsius * 9 / 5) + 32;
}
复制代码

编写另一个函数,它接受字符串类型的 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;
    // ...  
复制代码

解读

我们要想保持两个输入框中内容的同步,必须将子组件的状态移动到其共同的父组件中。

首先我们将 TemperatureInput 组件中的 this.state.temperature 替换为 this.props.temperature。

render() {
    // Before: const temperature = this.state.temperature;
    const temperature = this.props.temperature;
    // ...
复制代码

解读

由于props是只读的,由于temperature是通过props传递过来的,所以子组件现在失去了对它的控制权。所以此时我们要想继续修改temperature我们需调用this.props.onTemperatureChange 来更新它:

handleChange(e) {
    // Before: this.setState({temperature: e.target.value});
    this.props.onTemperatureChange(e.target.value);
    // ...
复制代码

官方提示:自定义组件中的 temperature 和 onTemperatureChange 这两个 prop 的命名没有任何特殊含义。我们可以给它们取其它任意的名字,例如,把它们命名为 value 和 onChange 就是一种习惯。

阶段性回顾

我们首先移出了组件自身的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>
    );
  }
}
复制代码

我们可以存储两个输入框中的值,但这并不是必要的。我们只需要存储最近修改的温度及其计量单位即可,根据当前的 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>
    );
  }
}
复制代码

梳理回顾整个流程

  1. React会调用每一个TemperatureInput中的handleChange方法
  2. 调用的handleChange方法均有父组件提供
  3. 无论哪个输入框的输入发生变化都会调用父组件更新两个输入框中的状态值

为什么任何可变数据应当只有一个相对应的唯一数据源?

带来的好处是,排查和隔离 bug 所需的工作量将会变少。由于“存在”于组件中的任何 state,仅有组件自己能够修改它,因此 bug 的排查范围被大大缩减了。

欢迎大家关注我的专栏,一起学习React!


相关文章
|
7月前
|
前端开发 JavaScript API
【第54期】一文读懂React文档
【第54期】一文读懂React文档
85 1
|
7月前
|
前端开发 JavaScript 定位技术
Docusaurus框架——react+antd+echarts自定义mdx生成图表代码解释文档
Docusaurus框架——react+antd+echarts自定义mdx生成图表代码解释文档
690 0
|
7月前
|
前端开发 JavaScript API
React 生态系统:路由、状态管理、调试、测试、组件库、文档……
React 生态系统:路由、状态管理、调试、测试、组件库、文档……
119 0
|
7月前
|
前端开发 安全 JavaScript
React 正式推出全新官方文档!
React 正式推出全新官方文档!
123 0
|
前端开发
react导出word文档?
react导出word文档?
|
前端开发 JavaScript 物联网
React组件库Concis | 组件突破50+,移动端concis起步,新增英语文档,持续更新中...
对于不熟悉这个项目的小伙伴们做个简单的介绍,Concis是一个基于React+TypeScript开发的一款轻量级组件库,全面拥抱React生态,支持React新特性(hooks/redux)追求轻量的组件体积,简单的使用方式,最小的思维负担。
116 1
React组件库Concis | 组件突破50+,移动端concis起步,新增英语文档,持续更新中...
|
前端开发 JavaScript 开发者
React组件状态
React组件状态
237 39
|
存储 前端开发 JavaScript
状态提升《react 前端》
通常,多个组件需要反映相同的变化数据,这时我们建议将共享状态提升到最近的共同父组件中去。让我们看看它是如何运作的。
128 1
|
前端开发 JavaScript
React中的一个状态管理工具—Flux
React中的一个状态管理工具—Flux
201 0
React中的一个状态管理工具—Flux
|
JavaScript 前端开发
React中的状态管理---Redux(上)
React中的状态管理---Redux
132 0
React中的状态管理---Redux(上)