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中必要掌握的技術基礎了

相關文章
相關標籤/搜索