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
- Each component has a updater that manages pendingCallbacks and pendingStates. PendingCallbacks is the second argument passed in when setState is called.
- The global updateQueue is used to manage the updater for each component.
- 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