React系列: setState的工作原理

2,330 阅读1分钟

简介

在hooks出现之前, setState是唯一的方式来改变组件的内部state. 本文梳理一下, setState的工作原理和执行流程.

setState的特征

  • 批量行为:React会合并多次setState操作为一次执行.
  • 异步:setState调用后,会调用其updater.addState,最终调用updateQueue.add将任务添加到队列等待系统批量更新batchUpdate.

执行过程

  1. 每一个组件都会有一个updater, 用来管理pendingCallbacks和pendingStates. 其中pendingCallbacks是调用setState时, 传入的第二个参数.
  2. 全局的updateQueue用来管理每一个组件的updater.
  3. updateQueue调用batchUpdate, 批量更新所有的updater.

执行流程

执行流程图

关键代码

// component

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

// updater

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))
  // ...
}