React setState流程解析【前端每日一题-22】

setState是异步的?setState更新流程是怎样的?

接触react后,在项目开发过程当中,发现setState的是异步方法,需要在回调函数中才能获得真正的值。

this.setState({
    num:this.state.num + 1 
},()=>{
    console.log(this.state.num);
});复制代码

react为什么会这样做呢?setState真的是异步的吗?

查阅相关文章后,才得知,并非如此。另外两种setState同步的做法。

//方法一:在this.setState之后去componentDidUpdate函数中调用,此时的this.state已经更新
componentDidUpdate(){
    console.log(this.state.num)
}

//方法二:在setTimeout函数中,在this.setState之后this.state是立即更新的,所以也可以获取到更新后的数据。
setTimeout(()=>{
    this.setState({
        num:this.state.num + 1
    });
    console.log(this.state.num);
})复制代码

看到这里,相信很多人都颠覆了之前对setState的异步理解,setState到底是如何进行组件state更新的呢?

setState 更新组件的流程

调用setState后,会把我们想要更新的state压进一个待更新队列(即内部实例的_pendingStateQueue),然后执行入栈更新操作enqueueUpdate,判定是否处于批量更新状态。如果正在进行组件的批量更新,那么久先把实例推进dirtyComponents里面等待下一次批量更新;相反若没有批量更新在执行,则会调用一个批量更新的事务。

/**
 * setState源码   
**/
 
ReactComponent.prototype.setState = function (partialState, callback) {
  this.updater.enqueueSetState(this, partialState); // 传入新state进队列
  if (callback) { // 推入callback队列
    this.updater.enqueueCallback(this, callback, 'setState')
  }
}
 
enqueueSetState: function(publicInstance, partialState) {
  // 获取内部实例
  var internalInstance = getInternalInstanceReadyForUpdate(publicInstance, 'setState')
  if (!internalInstance) {
    return
  }
 
  // 更新队列合并操作
  var queue = internalInstance._pendingStateQueue || (internalInstance._pendingStateQueue = [])
  queue.push(partialState)
 
  // 更新代码
  enqueueUpdate(internalInstance)
}复制代码

setState把我们希望更新的partialState推入待更新队列之后,就撒手交给enqueueUpdate去处理更新的时机了,我们看一下enqueueUpdate又为我们做了什么?

/**
 * enqueueUpdate源码   
**/
 
function enqueueUpdate(component) {
  ensureInjected()
 
  // 如果不处于批量更新模式
  if (!batchingStrategy.isBatchingUpdates) {
    batchingStrategy.batchedUpdates(enqueueUpdate, component)
    return
  }
  // 如果处于批量更新模式,则将该组件保存在 dirtyComponents 中
  dirtyComponents.push(component)
}复制代码

可以看到enqueueUpdate当中出现了一个重要的对象batchingStrategy,他有一个属性isBatchingUpdates用来告诉enqueueUpdate是应该更新,还是应该等待,把组件推入dirtyComponents里。可以想象这是一个react内部,用于控制批量更新的对象,让我们更近距离的了解它。

/**
 * batchingStrategy源码   
**/
 
var ReactDefaultBatchingStrategy = {
  isBatchingUpdates: false,
 
  batchedUpdate: function(callback, a, b, c, d, e) {
    var alreadyBatchingStrategy = ReactDefaultBatchingStrategy. isBatchingUpdates
    ReactDefaultBatchingStrategy. isBatchingUpdates = true
 
    if (alreadyBatchingStrategy) {
      callback(a, b, c, d, e)
    } else {
      transaction.perform(callback, null, a, b, c, d, e)
    }
  }
}复制代码

dirtyComponents当中提供的batchedUpdates其实就是我们一直寻找的,真实用来更新我们组件的方法。然而走到这一步,react却又向我们抛出了一个重大的概念——事务batchedUpdates当中transaction.perform就是事务的调用。

事务与componentDidMount

了解了setState执行的全过程,我们也清楚了这个函数其实并不一定是异步去执行的,倘若没有在进行更新dom时,它还是会立即触发dom的更新。

componentDidMount () {
  this.setState({ val: this.state.val + 1 })
  console.log(this.state.val) // 0
 
  this.setState({ val: this.state.val + 1 })
  console.log(this.state.val) // 0
}复制代码

按照刚才的想法,当componentDidMount执行的时候,按理说页面上已经有完整的dom渲染结束了,为什么此时我调用setState不能像setTimeout里一样,立即执行对state的更新呢?下面先抛出事务的简介。

简单来说,事务是一种react的处理机制,通过使用wrapper包裹你实际想要调用的方法,做一些前置initialize)和收尾close)的工作,所以在事务包裹的方法内,会优先触发前置钩子,以及执行完后会有收尾方法调用,这在react用作异常处理使用。

所以此时不难想到,其实componentDidMountreact挂载dom节点的事务的收尾工作,在这个环节操作state会被阻塞,直到事务完全执行完毕后,才会重新调用更新。

setState与生命周期

setState会触发组件的更新,同时在组件生命周期的钩子函数中我们往往会有对state的操作,操作不当很有可能发生state change =》 update =》 change state =》 state change……的死循环,那么哪些钩子函数内使用setState是安全的呢。

我们把生命周期钩子函数罗列出来

constructor -> 
componentWillMount -> 
render -> 
componentDidMount -> 
componentWillReceiveProps -> 
shouldComponentUpdate -> 
componentWillUpdate -> 
render -> 
componentDidUpdate复制代码

当中constructor中本身就有state的声明,在这里是最初的state创建,因此不需要使用setState

componentWillMount中,如果进行同步setState调用,此时的操作其实和constructor内定义state是一样的,并不会触发组件的额外渲染,当然这里可以做异步的setState操作,获取页面的初始数据。

rendershouldComponentUpdatecomponentWillUpdate这三个函数中,组件还没有渲染结束就继续调用setState,会无限触发更新,陷入死循环,是要注意的。

因此我们可用setState的生命周期钩子函数有:componentWillMountcomponentDidMountcomponentWillReceiveProps(react16 已废弃,直接使用getDerivedStateFromProps获取props并返回新的state)componentDidUpdate

至此setState的原理和使用就介绍完了,但是真正使用的契机却往往是前端开发者需要去琢磨的,对于非控制组件,这是react中必要掌握的技术基础了

参考:

React setState流程解析


转载于:https://juejin.im/post/5d492cd6f265da039f129f3a

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值