简单理解 React 状态提升

这段时间仔细看了看 React 的文档,收获不少,这篇文章我就来说一说其中的一点收获,状态提升

本文不涉及 双向绑定的内容

为什么有状态提升

因为有单向数据流,我们知道,在 React 中,每个组件只关心它内部的状态,甚至组件无法知道自己是函数组件还是 class 组件。所以我们的 state 为一个局部量,或者说是被封装起来的,它只对这个组件内部是有效的,在组件外面或者其他组件中不能直接使用这个组件的 state。

下面写一个很常见的受控组件

class NumberInput extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      number = '';
    }
    this.handleInputChange = this.handleInputChange.bind(this);
  }

  handleInputChange(e) {
    this.setState({number: e.target.value});
  }

  render() {
    return (
       <input
         value={this.state.number}
         onChange={this.handleInputChange} />
    )
  }
}

这个组件可以很容易的使用自己的 state,但要是别的组件要使用这里的值,那就需要一些操作了,也就是状态提升,将 state提升到它的父组件,然后它的父组件的所有子组件就能共享这个 state了。

React 便为我们提供了一个对象 props,它可以接收参数,并可以将它传递给子组件,这里要注意的是,props 是只读的,下面看一个例子

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      name: 'kevin',
    };
  }
  render() {
    return <SayHello name={this.state.name}/>;
  }
}

function SayHello(props) {
  return <h1>hello {props.name}</h1>;
}

可以看到,App是 class 类型的父组件,SayHello是函数类型的子组件,在父组件中,有一个 state,在render() 中 将 state.name 传递给了 SayHello 组件,这个传递过程用到的就是 props 。子组件接受 props 作为它的函数参数,这样,子组件内部就可以使用父组件给他的 name 属性了。

其实,React 不只规定一个组件不能直接访问另一组件的 state,它还规定一个组件的 state 的值只能由这个组件的 setState() 方法来改变,包括子组件使用 props 也不能直接更改,因为 props 是只读的。但是子组件可以调用父组件的方法,去更改父组件的 state,关于这部分,下面会有一个大栗子。

什么时候使用状态提升

我们可能在日常中遇到这样一个问题,两个组件需要共享数据,一起来完成某一项操作。那这个时候就可以使用到状态提升了。我们知道,React 的数据是向下流动的,所以,我们可以把这些共享的数据放到这两个组件的共同父组件中,那这两个组件就都能使用这一数据了。这个思想,就称为状态提升( Lefting State Up )。

使用状态提升

下面举一个例子,我们实现一个实时的进制转换,这里只转换 10 进制和 16 进制,来演示状态提升的用法。我们的需求是输入十进制的数字,立马就能将转换好的十六进制输出,反之亦然。下面是效果图

*pic,十进制十六进制实时转换效果图*

因为我暂时还没有过大项目的经验,所以一般都是从父组件开始,然后完成子组件,当然这个例子也比较适用于这样的流程。如果大家喜欢从子组件开始,然后完成父组件,那么可以去 React 官网,那里有一个例子

第一个输入框

完成一个简单的父组件,我们先添加一个十进制数的输入框组件,这个输入框为受控组件,以实现实时获取输入

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      number: '',
    }
    this.handleDecChange = this.handleDecChange.bind(this);
  }

  handleDecChange(number) {
    this.setState({number});
  }

  render() {
    const number = this.state.number;
    return (
      <div>
        <h1>十进制与十六进制互相转换</h1>
        <NumberInput 
        number={number}
        onNumberChange={this.handleDecChange}/>
      </div>
     )
  }
}

这个组件定义了自己的 state,里面有一个 number 属性,为字符串类型。有一个自己的方法handleDecChange( ),我们前面说了,一个组件的 state 只能由这一组件的方法来改变,并且只能通过 setState( ) 来改变。这里的 handleDecChange( ) 就是要传递给子组件,让子组件帮忙改变它的 state 的。

组件的事件处理程序必须绑定 this 到 该组件的构造函数中

这是因为在 React 的类组件中,当我们把事件处理函数引用作为回调传递过去, 事件处理程序方法会丢失其隐式绑定的上下文。当事件被触发并且处理程序被调用时,this的值会回退到默认绑定,即值为 undefined,这是因为类声明和原型方法是以严格模式运行。

详细的内容请看 这份优秀的资源

回到我们的代码中。render() 中,定义一个变量来保存 state 中的 number,方便调用。

之后就是这个组件返回到页面的内容。这里也需要注意,React 的 render() 只能返回一个节点,当然,这个节点可以包含任意多的子节点。上面返回了 div,里面包含了 NumberInput 节点,给该节点传递了一个 number 属性和一个onNumberChange()方法,这个方法就是父组件的 handleDecChange( ),以便子节点 NumberInput 来操作父节点 App 的内容( state )。

下面来看看子节点,其实就是文章开头的那个 受控组件 稍微改动了一点。

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

  handleInputChange(e) {
    this.props.onNumberChange(e.target.value );  // 使用父组件的方法修改父组件的state
  }

  render() {
    const number = this.props.number;
    return (
      <div>
        <span>十进制:</span>
        <input
          value={number}       // 使用父组件的 state
          onChange={this.handleInputChange} /> <br />
      </div>
    )
  }
}

子节点接收父组件的 props,前面看到,这个 props 中有一个属性 number,有一个方法 onNumberChange()。子组件有一个自己的方法 handleInputChange( ),这个方法调用父组件的 onNumberChange(),将 e.target.value传递进去,调用父组件的 setState() 修改父组件的 state。render() 方法中,有一个 span 标签,用来渲染标题,input 就是我们输入内容的 input 框,input 的 value 是 父组件的 number,有一个 onChange() 方法,用来监听输入框内容的变化,变化的时候就会调用 handeInputChange()。这样,就形成了一个完整的流程。

文章中,我是先写父组件,然后再写子组件,而实际运行过程中,当输入框中内容变化的时候,子组件会先运行,然后调用父组件的方法,修改父组件的 state 值,之后,React 本身发现 state 改变,便会触发元素渲染。

第二个输入框

上面我们只加了一个输入框,而我们的需求是要有两个输入框,实时进行进制转换。那么下面就添加第二个输入框

首先定义一个变量,来表示进制

const radixStr = {
  d: '十进制:',
  h: '十六进制:'
}

然后将父组件做一下修改

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      number: '',
      radix: 'd'          // 添加表示进制的 state 属性
    }
    this.handleDecChange = this.handleDecChange.bind(this);
    this.handleHexChange = this.handleHexChange.bind(this);   // 为新函数绑定 this
  }

  handleDecChange(number) {
    this.setState({radix: 'd', number});    // 将进制属性传递进去
  }

  handleHexChange(number) {     //	添加新的函数,当十六进制输入框变化的时候调用
    this.setState({radix: 'h', number});
  }
  
  render() {
    const radix = this.state.radix;
    const number = this.state.number;
    const dec = radix === 'h' ? toDec(number) : number;	// 判断进制,并进行转换
    const hex = radix === 'd' ? toHex(number) : number;
    return (
      <div>
        <h1>十进制与十六进制互相转换</h1>
        <NumberInput 
        radix='d'						// 表示进制的属性
        number={dec}
        onNumberChange={this.handleDecChange}/>
        <NumberInput 				// 十六进制的输入框组件
        radix='h'
        number={hex}
        onNumberChange={this.handleHexChange}/>
      </div>
    )
  }
}

对子组件也需要一些修改

class NumberInput extends React.Component {
……
  render() {
    const number = this.props.number;
    const radix = this.props.radix;			// 接收进制类型
    return (
      <div>
        <span>{radixStr[radix]}</span>	// 渲染出标题
        <input
          value={number}       // 使用父组件的 state
          onChange={this.handleInputChange} /> <br />
      </div>
    )
  }
}

上面父组件中有使用到两个方法,我一并贴到下面

function toDec(hex) {
  return (parseInt(hex + "", 16));
}
function toHex(dec) {
  return (Number(dec).toString(16));
}

这里只转换整数哈(真叫懒)

一个父组件,渲染两个不同的子组件,还要调用不同函数进行转换,是怎么个流程呢,下面就来说说

首先,可以看到子组件的变化仅仅只是在渲染标题上有不同,所以重头还是在父组件上。

父组件的 state 多了一个 radix 属性,表示进制的类型,默认为 ‘d’,也就是十进制。render() 方法中有这样一句

const dec = radix === 'h' ? toDec(number) : number;

这句会判断 state 中的进制,如果是十六进制,那么就调用 toDec() 将 number 其转换为十进制,如果是十进制,那就直接赋值。另外一个 hex 变量也是一个道理。

传递给子组件的两个函数,handleDecChange()handleHexChange()中会将进制类型进行了赋值,传递给 setState()方法。

下面我们整体说一下进制转换的过程,当我们在 十进制 的输入框中输入数字,就会触发子组件的 Input 元素里的 onChange() 方法,这个方法会调用 props.onNumberChange(),将 e.target.value 传递进去。props.onNumberChange() 就是父组件的 handleInputChange() ,而针对不同进制的输入框会调用不同的方法,我们当前是在十进制的输入框中改变了 value ,所以父组件就会调用 handleDecChange(),这个方法会修改 radix 为 ‘d’,即表示当前是十进制的输入框有变化,然后会将 state 中的 number 属性通过 setState 修改为从子组件传递来的 e.target.value。React发现 state 发生了变化,render() 方法便会执行(实际并没有这么简单,会涉及到生命周期,但先后顺序是没错的),dec 和 hex 两个变量就会进行判断赋值,最后,两个 NumberInput 组件分别带着自己的参数渲染出各自的组件,我们看到的就是两个输入框中的值都有变化。

总结

说了这么多,其实大多都说了其中的流程了,状态提升的思想其实并不难。就是将子组件的 state 提示到父组件,然后这个父组件的任何子组件就都能使用这个 state,从而达到多个组件之间共享 state 的目的。


文中多有不恰当之处,希望各位走过路过,多多指点。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值