React系列: setState的工做原理

簡介

在hooks出現以前, setState是惟一的方式來改變組件的內部state. 本文梳理一下, setState的工做原理和執行流程.javascript

setState的特徵

  • 批量行爲:React會合並屢次setState操做爲一次執行.
  • 異步:setState調用後,會調用其updater.addState,最終調用updateQueue.add將任務添加到隊列等待系統批量更新batchUpdate.

執行過程

  1. 每個組件都會有一個updater, 用來管理pendingCallbacks和pendingStates. 其中pendingCallbacks是調用setState時, 傳入的第二個參數.
  2. 全局的updateQueue用來管理每個組件的updater.
  3. updateQueue調用batchUpdate, 批量更新全部的updater.

執行流程

執行流程圖

關鍵代碼

// componentjava

setState(nextState, callback) {
  // 添加異步隊列 不是每次都更新
  this.$updater.addCallback(callback)
  this.$updater.addState(nextState)
}

複製代碼

// updaternode

getState() {
  let { instance, pendingStates } = this
  let { state, props } = instance
  
  // 合併待處理狀態數組
  if (pendingStates.length) {
    state = { ...state }
    pendingStates.forEach(nextState => {
      let isReplace = _.isArr(nextState)
      if (isReplace) {
        nextState = nextState[0]
      }
      if (_.isFn(nextState)) {
        // 函數方式將當即執行,它能夠得到以前合併的狀態結果 
        nextState = nextState.call(instance, state, props)
      }
      // replace state
      if (isReplace) {
        state = { ...nextState }
      } else {
        // 合併新舊狀態
        state = { ...state, ...nextState }
      }
    })
    pendingStates.length = 0
  }
  return state
}
複製代碼
// updater
addState(nextState) {
  if (nextState) {
    // 放入更新隊列
    this.pendingStates.push(nextState)
    // 若是當前隊列沒有工做則直接更新
    if (!this.isPending) {
      this.emitUpdate()
    }
  }
}
emitUpdate(nextProps, nextContext) {
  this.nextProps = nextProps
  this.nextContext = nextContext
  // receive nextProps!! should update immediately
  nextProps || !updateQueue.isPending
    ? this.updateComponent()
    : updateQueue.add(this)
}

// updateQueue
add(updater) {
  this.updaters.push(updater)
}
batchUpdate() {
  if (this.isPending) {
    return
  }
  this.isPending = true
  let { updaters } = this
  let updater
  while (updater = updaters.pop()) {
    updater.updateComponent()
  }
  this.isPending = false
}

//updater
updateComponent() {
  let { instance, pendingStates, nextProps, nextContext } = this
  if (nextProps || pendingStates.length > 0) {
    // ...
    // getState 合併全部的state的數據,一次更新
    shouldUpdate(instance, nextProps, this.getState(), nextContext,
      this.clearCallbacks)
  }
}
function shouldUpdate(component, nextProps, nextState, nextContext, callback) {
  component.forceUpdate(callback)
}

// Component
// 跳過全部生命週期執行強制更新
forceUpdate(callback) {
  // 實際更新組件的函數
  let { $updater, $cache, props, state, context } = this
  //...
  // 下面纔是重點 diff
  let newVnode = renderComponent(this)
  let newNode = compareTwoVnodes(vnode, newVnode, node, getChildContext(this,
    parentContext))
  // ...
}
複製代碼
相關文章
相關標籤/搜索