状态提升

通常,几个组件拥有相同的数据,此时就需要将这个数据放到最近的公共祖先的状态里。

接下来,我们将创建一个温度计算器,并根据给定水温判断是否沸腾。最终效果如图所示:
result

也就是两个输入框分别对应摄氏温度和华氏温度,只要有一个输入框内容变化,另一个也会相应变化,并判断是否会沸腾。

我们先考虑只有摄氏温度输入框的情况,

class Calculator extends React.Component {
    constructor(props) {
      super(props);
      this.state={
        temperature: ''
      };
      this.handleChange = this.handleChange.bind(this);
    }

    handleChange(e) {
      this.setState({temperature: e.target.value})
    }

    render() {
      return (
        <fieldset>
          <legend>Enter temperature in celsius:</legend>
          <input onChange={this.handleChange} />
          <BoilingVerdict celsius={parseFloat(this.state.temperature)}>
          </BoilingVerdict>
        </fieldset>
      )
    }
  }      
  function BoilingVerdict(props) {
    if (props.celsius >= 100) {
      return <p>The water would boil.</p>;
    }
    return <p>The water would not boil.</p>;
  }

  ReactDOM.render(
    <Calculator />,
    document.getElementById('root')
  );

接着我们再考虑有两个输入框的情况,这时候应该把输入框单独作为一个组件提取出来:

class TemperatureInput extends React.Component {
    constructor(props) {
      super(props);
      this.state={
        temperature: ''
      };
      this.handleChange = this.handleChange.bind(this);
    }

    handleChange(e) {
      this.setState({temperature: e.target.value})
    }

    render() {
      return (
        <fieldset>
          <legend>Enter temperature in {this.props.scale}:</legend>
          <input onChange={this.handleChange} />
          <BoilingVerdict celsius={parseFloat(this.state.temperature)}>
          </BoilingVerdict>
        </fieldset>
      )
    }
  }      

  const scaleName = {
    'c': 'celsius',
    'f': 'Fahrenheit'
  }

  class Calculator extends React.Component {
    render() {
      return (
        <div>
          <TemperatureInput scale={scaleName.c}/>
          <TemperatureInput scale={scaleName.f}/>
        </div>
      )
    }
  }      
  function BoilingVerdict(props) {
    if (props.celsius >= 100) {
      return <p>The water would boil.</p>;
    }
    return <p>The water would not boil.</p>;
  }

  ReactDOM.render(
    <Calculator />,
    document.getElementById('root')
  );

好,现在得到了两个输入框,可是这两个输入框是独立的,一个框的内容不会同步到另一个,由于以前使用vue的时候,我会想到非父子组件通信,也就是通过两个子组件的共同父组件来传递值,react这里也一样,遵循的原则是数据流top-down-flow,所以也不应该考虑一个组件传值给另一个组件,也应该采用公共组件的方式,那么这里我们就应该把我们想要的值放到公共组件Calculator里.

整个工作流程应该是这样的:两个组件都接受父组件的数据,组件一数据变化了,就去触发父组件的事件,这个被触发的事件就去更新父组件的状态,这一更新状态,react就要去重新render了,因为这时组件二的数据也随着变了,所以也就同步显示了。

class TemperatureInput extends React.Component {
    constructor(props) {
      super(props);
      this.handleChange = this.handleChange.bind(this);
    }

    handleChange(e) {
      this.props.onTemperatureChange(e.target.value)
    }

    render() {
      return (
        <fieldset>
          <legend>Enter temperature in {this.props.scale}:</legend>
          <input value={this.props.temperature} onChange={this.handleChange}/>
          <BoilingVerdict celsius={this.props.temperature}>
          </BoilingVerdict>
        </fieldset>
      )
    }
  }      

  const scaleName = {
    'c': 'celsius',
    'f': 'Fahrenheit'
  }

  class Calculator extends React.Component {
    constructor(props) {
      super(props);
      this.state={
        temperature: '',
        scale: 'c'
      };
      this.handleCelsiusChange=this.handleCelsiusChange.bind(this);
      this.handleFahrenheitChange=this.handleFahrenheitChange.bind(this);
    }

    handleCelsiusChange(temperature) {
      this.setState({scale:'c', temperature});
    }
    handleFahrenheitChange(temperature) {
      this.setState({scale:'f', temperature});
    }

    render() {
      const temperature = this.state.temperature;
      const scale = this.state.scale;
      const celsius = scale === 'f' ? tryConvert(temperature, toCelsius) : temperature;
      const fahrenheit = scale === 'c' ? tryConvert(temperature, toFahrenheit) : temperature;

      return (
        <div>
          <TemperatureInput scale={scaleName.c} temperature={celsius} onTemperatureChange={this.handleCelsiusChange}/>
          <TemperatureInput scale={scaleName.f} temperature={fahrenheit} onTemperatureChange={this.handleFahrenheitChange}/>
        </div>
      )
    }
  }      
  function BoilingVerdict(props) {
    if (props.celsius >= 100) {
      return <p>The water would boil.</p>;
    }
    return <p>The water would not boil.</p>;
  }

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

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

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


  ReactDOM.render(
    <Calculator />,
    document.getElementById('root')
  );