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

setState是異步的?setState更新流程是怎樣的?

接觸react後,在項目開發過程中,發現setState的是異步方法,須要在回調函數中才能得到真正的值。javascript

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裏面等待下一次批量更新;相反若沒有批量更新在執行,則會調用一個批量更新的事務。
java

/** * 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又爲咱們作了什麼?
react

/** * 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就是事務的調用。
app

事務與componentDidMount

瞭解了setState執行的全過程,咱們也清楚了這個函數其實並不必定是異步去執行的,假若沒有在進行更新dom時,它仍是會當即觸發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會被阻塞,直到事務徹底執行完畢後,纔會從新調用更新。ui

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流程解析

相關文章
相關標籤/搜索