Introduction to the

Before hooks came along, setState was the only way to change the internal state of a component. This paper summarizes the working principle and execution process of setState.

The characteristics of the setState

  • Batch behavior: React merges multiple setState operations into a single execution.
  • Asynchronous: After setState is called, updater.addState is called, and finally updatequue. Add is called to add tasks to the queue for batchUpdate in batches.

Implementation process

  1. Each component has a updater that manages pendingCallbacks and pendingStates. PendingCallbacks is the second argument passed in when setState is called.
  2. The global updateQueue is used to manage the updater for each component.
  3. UpdateQueue calls batchUpdate to update all updaters in batches.

Execute the process

The key code

// component

setState(nextState, callback) {
  // Adding asynchronous queues is not updated every time
  this.$updater.addCallback(callback)
  this.$updater.addState(nextState)
}

Copy the code

// updater

getState() {
  let { instance, pendingStates } = this
  let { state, props } = instance
  
  // Merge pending state arrays
  if(pendingStates.length) { state = { ... state } pendingStates.forEach(nextState= > {
      let isReplace = _.isArr(nextState)
      if (isReplace) {
        nextState = nextState[0]}if (_.isFn(nextState)) {
        // The function mode will execute immediately, and it can get the result of the previously merged state
        nextState = nextState.call(instance, state, props)
      }
      // replace state
      if(isReplace) { state = { ... nextState } }else {
        // Merge old and new statesstate = { ... state, ... nextState } } }) pendingStates.length =0
  }
  return state
}
Copy the code
// updater
addState(nextState) {
  if(nextState) {this.pendingStates.push(nextState) // Update if the current queue is not workingif(! 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) { // ... ShouldUpdate (instance, nextProps, this.getState(), nextContext, this.clearcallbacks)}}functionshouldUpdate(component, nextProps, nextState, nextContext, Callback) {component.forceUpdate(callback)} // component // Skip all life cycles to perform mandatory updates forceUpdate(callback) {// The function that actually updates the componentlet { $updater.$cache, props, state, context } = this //... // The following is the key difflet newVnode = renderComponent(this)
  let newNode = compareTwoVnodes(vnode, newVnode, node, getChildContext(this,
    parentContext))
  // ...
}
Copy the code