React setState流程解析

1、setState使用

接触react框架不久,却在项目当中发现,非受控组件其更新时机的触发方式——setState,是一个异步的过程javascript

下面是一个例子:前端

handelTabChange (tabName) {
  this.setState({
    tab: tabName
  })
  this.updateTabPane()
}

updateTabPane () {
  const { tab } = this.state
  console.log( tab ) // not the latest one
}

此时tab发生变化的时候,输出的却依然是上一个tab的名称,所以能够判断updateTabPane是在setState以前执行了。
那么,为何setState须要异步去改变组件的state呢?java

React组件是靠单向数据流构建页面dom的,除开props,自身的state改变也是引发组件渲染的主要因素,为了节省性能消耗,react有一套本身的state更新策略,为的是减小state更新对页面渲染的消耗,而这套更新策略的入口就是setState方法。react

这一样也解释了为何直接对this.state进行赋值操做并不能改变页面的渲染结果。安全

那么在异步的过程当中,setState究竟作了哪些事儿呢?app

2、 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又为咱们作了什么dom

/**
 * 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就是事务的调用函数

3、 事务与componentDidMount

了解了setState执行的全过程,咱们也清楚了这个函数其实并不必定是异步去执行的,假若没有在进行更新dom时,它仍是会当即触发dom的更新

//this.state.val = 0
setTimeout(() => {
  this.setState({val: this.state})
  console.log(this.state.val) // 1

  this.setState({val: this.state})
  console.log(this.state.val) // 2
}, 0)

当他异步的时候,从setState的源码中咱们也看到了,若是想要在新的state更新后触发操做,只须要在setState的第二个参数当中传入你想要执行的回调便可。

可是,若是想要解释如下现象,咱们还须要向你们介绍事务的概念。

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会被阻塞,直到事务彻底执行完毕后,才会从新调用更新。

4、 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的生命周期钩子函数有:componentWillMountcomponentDidMountcomponentWillReceivePropscomponentDidUpdate


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

相关文章
相关标签/搜索