React起手-提炼状态

官方文档叫Lifting State up,不知道我翻译的是否妥当,哈哈哈。

组件之间的数据有些肯定是共享的,也就是说这个共享数据不管在什么地方被改变了,所有组件都要及时的反应到自身上,官方推荐提炼共有状态到他们最近的祖先上。举个例子来理解这个东东,例如有个温度计,当温度到100以上时候水开,反之不开。

function BoilingVerdict(props) {
  if (props.celsius >= 100) {
    return <p>The water would boil.</p>;
  }
  return <p>The water would not boil.</p>;
}

然后我们添加一个计算器来输入温度

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呢?一个输入的是摄氏度,一个输入的是华氏度。

首先明确下,第一步,我们接下来需要一个计算器组件来显示显示两个input组件:

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>
    );
  }
}
class Calculator extends React.Component {
  render() {
    return (
      <div>
        <TemperatureInput scale="c" />
        <TemperatureInput scale="f" />
      </div>
    );
  }
}

可以看到两个TemperatureInput组件是相互独立的,也就是说他们各自保有自己的==this.state = {value: ''};==所以输入其中一个另一个并不会相应的更新,其实我们不仅要让他们显示的时候互相更新,而且还需换算后更新。

有什么办法能让两个TemperatureInput的状态取得联系呢?没错,就是提炼共有状态到他们最近的父组件即可。在此之前我们先准备好换算函数tryConvert:

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

接下来我们重写TemperatureInput:

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});
    this.props.onChange(e.target.value);
  }

  render() {
    // 干掉 const value = this.state.value;
    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>
    );
  }
}

简单讲就是把私有状态的操作变成组件属性的操作。接下来就是构建其父组件,并且把共享状态放在这个地方,并通过属性传递给子组件使用:

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

从上面我们可以看到如果变量可以从另一个变量中派生出来的时候,我们就不要把这些变量设为state,例如celsiusValue和fahrenheitValue可以通过value 和 scale进行计算,所以就不用提炼出来,应该在render方法里直接计算得到即可。

初步看起来有些复杂,但这也是React所提倡的从上至下单向数据流动的体现,其实这个需求用vue的双向绑定和计算属性会很容易完成,新入门可能还不能很好的体会到这样做的优缺点,作为入门,我们可以多看看代码,先熟悉下这种感觉,之后实际项目中也许某些时候会给你一些灵感和理解吧~