State提升

通常,一些组件需要对同一个数据做出反应,这时我们建议将这些组件中关于这个数据的State提升至和它们距离最近的父级组件中。让我看看该如何做到这一点:
在这个章节,我们将会创建一个温度计算器,根据用户给定数值来计算水是否沸腾。
我们首先创建一个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控件,并将用户输入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>
    );
  }
}

在添加一个input

我们新的要求是这样的,现在需要另一个Fahrenheit温度的输入框,它和上述Celsius的输入框内的值保持同步。
首先我们可以将TemperatureInput组件从Calculator组件中抽象出来,我们添加一个scale的prop用来区分输入框是Celsius还是Fahrenheit:

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>
    );
  }
}

现在我们有两个输入框了,但是当我们给其中一个输入框输入内容时,另一个输入框的内容不会同步改变,这不符合我们我们的需求。由于当前的温度值被隐藏在TemperatureInput组件的内部,所以Calculator组件无法知道当前的温度值,BoilingVerdict组件也就无法知道知道水是否沸腾。

State提升

首先我们需要两个函数,它们分别将fahrenheit转为Celsius,将Celsius转为ahrenheit:

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

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

上面的两个函数都起到转换数值的作用。我们在另外写一个函数,这个函数接受一个字符串和一个函数作为参数,返回值为一个字符串。当输入一个非数字的值时,它返回一个空字符串:

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();
}
For example, tryConvert('abc', toCelsius) returns an empty string, and tryConvert('10.22', toFahrenheit) returns '50.396'.

Next, we will remove the state from TemperatureInput.

Instead, it will receive both value and the onChange handler by props:

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>
    );
  }
}

如果一些组件需要用相同的state时,只需要把这个state提升至最近的父组件内就好。在我们的例子中,这个组件便是Calculator,我们把value和scale储存到它里面。
我们可以把两个输入框的值都储存起来,但是这种做法没有必要。我们只要储存最近一次的数值和scale就可以了,因为我们可以根据value 和scale来计算另一个scale的value:

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;
    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.value值和this.state.scale值都会被更新,这样另一个没被输入数值的输入框的值也会相应的被改变。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • React版本:15.4.2**翻译:xiyoki ** 通常几个组件需要响应相同的数据变化。我们建议将共享状态提...
    前端xiyoki阅读 4,101评论 0 0
  • 通常,几个组件需要根据同一个数据变化做出响应。我们建议将这个共享的状态提升到他们最近的一个共用祖先。让我们看看实际...
    莫铭阅读 4,379评论 0 1
  • 最近看了一本关于学习方法论的书,强调了记笔记和坚持的重要性。这几天也刚好在学习React,所以我打算每天坚持一篇R...
    gaoer1938阅读 5,669评论 0 5
  • 一 一直在追的热剧《人民的名义》,昨晚终于落下帷幕。 这部戏的故事情节折射出残酷现实和复杂人性,具有极强的感染力,...
    柴门内外阅读 9,023评论 8 14
  • 记得去年我去宁波参加一个的婚恋课程,遇到这样一件非常有趣的事情,我在会场认识了一个叫小孔(化名)的男孩子,这个小孔...
    魏康2019阅读 3,138评论 0 0