美文网首页
9. Lifting State Up(状态提升)

9. Lifting State Up(状态提升)

作者: 前端xiyoki | 来源:发表于2017-02-17 11:56 被阅读0次

    React版本:15.4.2
    **翻译:xiyoki **

    通常几个组件需要响应相同的数据变化。我们建议将共享状态提升到最接近的共同祖先。让我们看看这是如何工作的。
    在本节中,我们将创建一个温度计算器,用于计算水是否在给定温度下沸腾。
    我们将从名为BoilingVerdict的组件开始。它接受celsius温度为props,并打印是否足以将水烧开:

    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.value中。
    此外,它用当前值渲染BoilingVerdict

    class Calculator extends React.Component {
      constructor(props) {
        super(props);
        this.handleChange = this.handleChange.bind(this);
        this.state = {value: ''};
      }
    
      handleChange(e) {
        this.setState({value: e.target.value});
      }
    
      render() {
        const value = this.state.value;
        return (
          <fieldset>
            <legend>Enter temperature in Celsius:</legend>
            <input
              value={value}
              onChange={this.handleChange} />
            <BoilingVerdict
              celsius={parseFloat(value)} />
          </fieldset>
        );
      }
    }
    

    Adding a Second Input(增加第二个输入)

    我们新的要求是:除了摄氏度输入框,我们还提供华氏度输入框,并且二者是同步的。
    我们把从Calculator中提取一个TemperatureInput组件作为开始。我们将为它增加一个新scale prop,并且这个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 = {value: ''};
      }
    
      handleChange(e) {
        this.setState({value: e.target.value});
      }
    
      render() {
        const value = this.state.value;
        const scale = this.props.scale;
        return (
          <fieldset>
            <legend>Enter temperature in {scaleNames[scale]}:</legend>
            <input value={value}
                   onChange={this.handleChange} />
          </fieldset>
        );
      }
    }
    

    现在,我们可以改变Calculator来渲染两个独立的温度输入:

    class Calculator extends React.Component {
      render() {
        return (
          <div>
            <TemperatureInput scale="c" />
            <TemperatureInput scale="f" />
          </div>
        );
      }
    }
    

    现在我们有两个输入框,但当你向其中一个输入框输入温度时,另一个并不会更新。这违反了我们的要求:我们希望它们保持同步。
    我们也不能从Calculator中展示BoilingVerdictCalculator也不知道当前的温度,因为当前温度被隐藏在了TemperatureInput内部。

    Lifting State Up(状态提升)

    首先,我们将写两个函数来将摄氏度转换为华氏度,将华氏度转换为摄氏度:

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

    这两个函数转换数字。我们将写另一个函数,它接受一个字符串value和一个转换器函数作为参数,并返回一个字符串。我们将使用它来计算其中一个输入框的值,而该输入框的值基于另一个输入框。
    它返回一个无效的空字符串value,并且它将输出四舍五入到三位小数:

    function tryConvert(value, convert) {
      const input = parseFloat(value);
      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中删除状态。
    相反,TemperatureInput组件将接受valueonChange处理程序作为prop:

    class TemperatureInput extends React.Component {
      constructor(props) {
        super(props);
        this.handleChange = this.handleChange.bind(this);
      }
    
      handleChange(e) {
        this.props.onChange(e.target.value);
      }
    
      render() {
        const value = this.props.value;
        const scale = this.props.scale;
        return (
          <fieldset>
            <legend>Enter temperature in {scaleNames[scale]}:</legend>
            <input value={value}
                   onChange={this.handleChange} />
          </fieldset>
        );
      }
    }
    

    如果几个组件需要访问相同的状态,这标志着状态应该被提升到最接近的共同祖先。在这个例子中最接近的祖先就是Calculator。我们将在它的状态中存储当前的valuescale
    我们可以存储两个输入的值,但事实证明这是不必要的。它足以存储最近被更改的输入框的值,以及其表示的scale。然后我们能基于当前的valuescale,单独推断其他输入框的值。
    输入的值保存同步,因为它们的值从相同的状态计算而来。

    class Calculator extends React.Component {
      constructor(props) {
        super(props);
        this.handleCelsiusChange = this.handleCelsiusChange.bind(this);
        this.handleFahrenheitChange = this.handleFahrenheitChange.bind(this);
        this.state = {value: '', scale: 'c'};
      }
    
      handleCelsiusChange(value) {
        this.setState({scale: 'c', value});
      }
    
      handleFahrenheitChange(value) {
        this.setState({scale: 'f', value});
      }
    
      render() {
        const scale = this.state.scale;
        const value = this.state.value;
        const celsius = scale === 'f' ? tryConvert(value, toCelsius) : value; {/* 对状态value作进一步处理*/}
        const fahrenheit = scale === 'c' ? tryConvert(value, toFahrenheit) : value;
    
        return (
          <div>
            <TemperatureInput
              scale="c"
              value={celsius}
              onChange={this.handleCelsiusChange} />
            <TemperatureInput
              scale="f"
              value={fahrenheit}
              onChange={this.handleFahrenheitChange} />
            <BoilingVerdict
              celsius={parseFloat(celsius)} />
          </div>
        );
      }
    }
    

    不论你编辑哪个输入框,Calculator中的this.state.valuethis.state.scale都会获得更新。其中一个输入框获取的值为原样,因此任何用户的输入都被保留,另一个输入框中的值总是基于它重新计算。

    Lessons Learned (得到的教训)

    对于在React应用程序中更改的任何数据,应该有一个单一的‘真实来源’。通常,首先将状态添加到需要渲染的组件。然后,如果其他组件也需要它,你可以将其提升到最接近的共同祖先。而不是尝试在不同组件之间同步状态,你应该依赖于自上而下的数据流。
    提升状态涉及编写比双向绑定方法更多的‘样板’代码。但好处是找到和隔离bug需要较少的工作。由于任何状态存在于特定的组件中,并且该组件可以单独改变它,所以大大减少了错误的表面积。此外,你可以实现任何自定义逻辑以拒绝或转换用户输入。
    如果数据可以从props或state派生,那么它就不应该在状态之中。例如,我们只存储了最后编辑的valuescale,而不是存储两个celsiusValuefahrenheitValue。另一个输入的值总是可以从render()方法中计算出来。这允许我们清除或应用四舍五入到其他字段,而不会丢失用户输入的任何精度。
    当你在UI中看到错误时,可以使用 React Developer Tools 检查props,并向上移动树,直到找到负责更新状态的组件。这使你可以跟踪错误到其来源:

    相关文章

      网友评论

          本文标题:9. Lifting State Up(状态提升)

          本文链接:https://www.haomeiwen.com/subject/uxnkwttx.html