简介
在hooks出现之前, setState是唯一的方式来改变组件的内部state. 本文梳理一下, setState的工作原理和执行流程.
setState的特征
- 批量行为:React会合并多次setState操作为一次执行.
- 异步:setState调用后,会调用其updater.addState,最终调用updateQueue.add将任务添加到队列等待系统批量更新batchUpdate.
执行过程
- 每一个组件都会有一个updater, 用来管理pendingCallbacks和pendingStates. 其中pendingCallbacks是调用setState时, 传入的第二个参数.
- 全局的updateQueue用来管理每一个组件的updater.
- 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))
// ...
}