react的setState怎么用以及解析

4 篇文章 0 订阅

setState的异步和多次合并

在react官网的State&生命周期篇章中有详细介绍关于State以及setState的用法

  tick() {
    this.setState({
      date: new Date()
    });
  }

最基础的用法就是上面这种例子,但是这种用法有一个问题

    this.setState({
      a: this.state.a + 1
    })
    this.setState({
      a: this.state.a + 1
    })

当我同时调用两次的时候,结果并不是我想象的 a + 2,而是 a + 1
这是因为在react调用的时候采用了异步更新的机制,在异步更新的过程中会将setState的执行全部放在一个queue中,在render执行之前合并多次更新

  componentDidMount() {
    this.setState({
      a: this.state.a + 1
    })
    this.setState({
      a: this.state.a + 1
    })
    console.log("this.state.a = " , this.state.a)  // this.state.a =  1
  }
  render() {
    console.log("render")
    const { a } = this.state;
    return (<div id="a">
      {a}
    </div>)
  }

上面的代码 “render”只会执行两次,第一次是初始化,第二次是两个setState合并后的执行
由此可以看出,setState的一些特性

  • 异步执行
  • 可能会自动合并多次setState
    这是react为了性能考虑而做的优化,但是这也有一个问题,假设我需要获得一个执行出 a + 2的结果时就需要换一种方式实现了

拒绝setState的合并

官网方式

  this.setState((state:any) => {
    return {
      a: state.a + 1
    }
  })
  this.setState((state:any) => {
    return {
      a: state.a + 1
    }
  })

这种方式的处理是官网推荐的处理方式,强烈建议使用,虽然多了几行代码,但是真的很优秀,不要嫌写回调函数麻烦
下面看另一种方式

  setTimeout(() => {
    console.log(this.state)
    this.setState({
      a:4
    })
    this.setState({
      a:2
    })
  },0)

这种方式也能实现setState的拒绝合并,但是两者有一个很重要的区别,我们观察一下在render方法中的调用
官网方式的
在这里插入图片描述
执行两次render
setTimeOut的
在这里插入图片描述
执行了三次render,发现区别了吗?这就是为什么强烈推荐回调函数写法的原因

setState执行的过程

这里推荐一个很好懂的setState实现过程可以帮助你理解一下setState的运行过程
首先setState执行的时候是将当前的事件存入队列中,随后可以利用js的时间循环机制来确定什么时候把时间队列清空
首先定义一个往队列里添加内容的方法

const queue = [];
function enqueueSetState( stateChange, component ) {
  // 如果queue的长度是0,也就是在上次flush执行之后第一次往队列里添加
  if ( queue.length === 0 ) {
    // 按照事件循环机制,fn将在同步序列执行完之后首先执行
    Promise.resolve().then( fn );
  }
  queue.push( {
      stateChange,
      component
  } );
}

清空队列的方法

function flush() {
  let item;
  // 遍历
  while( item = setStateQueue.shift() ) {

      const { stateChange, component } = item;

      // 如果没有prevState,则将当前的state作为初始的prevState
      if ( !component.prevState ) {
          component.prevState = Object.assign( {}, component.state );
      }

      // 如果stateChange是一个方法,也就是setState的第二种形式
      if ( typeof stateChange === 'function' ) {
          Object.assign( component.state, stateChange( component.prevState, component.props ) );
      } else {
          // 如果stateChange是一个对象,则直接合并到setState中
          Object.assign( component.state, stateChange );
      }
		// 注意这里,component.state是原值并没有改变,所以每次合并Object.assign(component.state, stateChange);而不是Object.assign(component.prevState, stateChange);
      component.prevState = component.state;

  }
}

当我们执行setState方法时

setState( stateChange ) {
  enqueueSetState( stateChange, this );
}

从这时候开始,flush就会被添加进异步队列里等待同步执行完成后执行,这期间所有的stateChange都会被放进队列中等待flush执行的时候合并,具体的实现还是看setState实现过程

看完这里你应该也就理解了为什么上面用官方推荐的方式不会多执行一次render了

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

霜叶w

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值