react组件状态提升--温度转换案例

状态提升:使用react经常遇到组件共用状态数据的情况,将这部分共享状态提升到他们最近的父组件当中进行管理。

创建一个温度计算器来计算水是否会在给定的温度下烧开。

首先创建一个名为BoilingVerdict 的组件。他会接受 celsius 这个温度变量最为它的props属性,最后根据温度判断返回内容:

function BoilingVerdict(props){
if(props.celsius >=100){
return <p>水会烧开</p>;
}
return <p>水不会烧开</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>输入一个摄氏温度</legend>
        <input
          value={temperature}
          onChange={this.handleChange} />

        <BoilingVerdict
          celsius={parseFloat(temperature)} />

      </fieldset>
    );
  }
}

添加第二个输入框:

在提供摄氏度的基础上,再提供一个华氏温度输入。

我们从Calculator 组件中抽离一个TemperatureInput组件,加一个值为c或者f的表示温度当我scale属性

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>
            <Temperature scale = 'c'/>
            <Temperature scale = 'f'/>
        </div>
        )
    }

}

现在有了两个输入框,但是其中一个输入时,另一个并不会更新,另外我们此时不能从Calculator 组件中展示BoilingVerdict 的渲染结果。因为现在表示温度的状态数据只存在于TemperatureInput组件当中。

写出转换函数、

首先,我们写两个可以将摄氏度和华氏度相互转换的函数。

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

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

我们还需要一个转换函数(字符串):

取到输出小数点后三位,当temperature 输入不合法的时候,这个函数则会返回空字符串。

//转换函数
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();
}

状态提升

到这一步为止,两个TemperatureInput组件都在自己的state里面单独保存数据。

但是我们想要这两个输入框保持同步。

在react中 ,状态分析是通过将state数据提升至离需要这些数据的组件的父组件来完成的。这就是所谓的状态表提升。

我们会将TemperatureInput 组件自身保存的state 移到Calculator中,

如果Claculator 组件拥有了提升上来共享的状态数据,那他就会成为两个温度输入组件的“数据源”,它会传递给下面温度输入组件一致的数据。由于两个TemperatureInput 温度组件的props属性都是来源于共同的父组件Calculator,

他们的数据也会保持同步。

首先,我们在TemperatureInput 组件中将this.state.temperature 替换为this.props.temperature.从现在开始,我们假定this.props.temperature属性已经存在了,不过之后仍然需要将数据从Calculator组件中传进去

render(){
        //之前的代码  const Temperature = this.state.Temperature;
        const Temperature = this.props.Temperature;

props 是只读的,所以现在temperature 变量作为props从父组件传递下来的,TemperatureInput 组件失去了控制权。

在react中,这个问题通常让组件“受控”来解决的。自定义组件 TemperatureInput 也能接受来自 Calculator 父组件的 temperature 变量和 onTemperatureChange 方法作为props属性值。

调用 this.props.onTemperatureChange 方法就可以更新temperature的值。


    handleChange(e){
        
        //之前的代码: this.setState({Temperature:e.target.value});
        this.props.onTemperatureChange(e.target.value);
    }

当我们想要响应数据改变时,使用父组件提供的 this.props.onTemperatureChange() 而不是this.setState() 方法:

代码:

 

//用于 显示
const scaleNames = {
  c: 'Celsius',
  f: 'Fahrenheit'
};

//tryConvert 的 convert(input)
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();
}

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

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

  handleChange(e) {
    //关键点 props调用父组件   e.target.value 修改值
    this.props.onTemperatureChange(e.target.value);
  }
  //渲染值,当输入框的值改变时 首先调用的是这里的onChange
  render() {
    const temperature = this.props.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 {
  constructor(props) {
    super(props);
    this.handleCelsiusChange = this.handleCelsiusChange.bind(this);
    this.handleFahrenheitChange = this.handleFahrenheitChange.bind(this);
    this.state = {temperature: '', scale: 'c'};
  }

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

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

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

    return (
      <div>
        <TemperatureInput
          scale="c"
          temperature={celsius}
          onTemperatureChange={this.handleCelsiusChange} />
        <TemperatureInput
          scale="f"
          temperature={fahrenheit}
          onTemperatureChange={this.handleFahrenheitChange} />
        <BoilingVerdict
          celsius={parseFloat(celsius)} />
      </div>
    );
  }
}

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

基本调用过程: 

1、React会检测到DOM元素的onchange事件,然后进行响应,先找到了TemperatureInput的handleChange 函数

2、handleChange函数调用了this.props.onTemperatureChange,由于我们进行bind绑定,React就找到了父组件Calculator

3、执行this.props.onTemperatureChange函数时,改变了父组件的state状态,即调用了this.setState()

4、React检测到父组件Calculator的状态变化,然后调用它的render函数,然后render函数又调用了TemperatureInput,更新了视图

 

技术点学习:

1.请看Calculator中的render()中有个tryConvert(temperature, toCelsius)  toCelsius函数的名称,也就是toCelsius当做对象传入

2.请看Calculator中handleCelsiusChange(temperature)设置值  this.setState({scale: 'c', temperature}); 中temperature本应该是键值对的形式,直接更新temperature值

3.观察temperature值的传递从

        a.TemperatureInput 中handleChange()中 this.props.onTemperatureChange(e.target.value);

        b.Calculator 中render()中<TemperatureInput scale="c"   temperature={celsius} onTemperatureChange={this.handleCelsiusChange} />

        c.Calculator 中handleCelsiusChange(temperature)   this.setState({scale: 'c', temperature});

        d.temperature转换成celsius或fahrenheit值

        e.TemperatureInput 中render()中    const temperature = this.props.temperature;  <input value={temperature} onChange={this.handleChange} />

4. Number.isNaN(input)判断是不是数字

5. ===表示 类型 和 值都相等
6. Math.round(output * 1000) / 1000;保留三个小数

7. parseFloat(celsius)将值转换为float的值

 

小小的温度案例,知识点还是挺多的,写久了JavaScript1,react的思想还是要慢慢的培养。

                                                                                                                        2018.10.29    用友产业园

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值