React - Lifting state up 提升状态

  • 通常,我们需要几个不同的组件来显示相同的数据变化。我们建议提升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);
}

猜你喜欢

转载自blog.csdn.net/creatint/article/details/72732071