前言须知
jsx的渲染过程
jsx会经过babel编译成createElement函数的结构,然后createElement执行产生虚拟dom结构VNode(就是一个有一定属性的对象结构),然后通过render函数处理VNode为虚拟节点,在页面中渲染。详细请点击此处dom的更新过程
diff算法对比新旧VNode,如果新旧VNode不一样就调用render重新渲染视图的过程。详细请点击此处
回归正题
setState的使用
为了避免每执行一次 setState,就重新生成 newVNode 进行 diff。,会造成主进程的阻塞,页面的卡死,所以setState做了异步处理和合并执行(多次连续调用会被最终合并成一次)的两种优化。
异步处理的实现可以通过promise来实现
- setState的实现
let updateQueue=[];
function enqueueRender(updater) {
// 将所有 updater 同步推入更新队列中
// 为实例添加一个属性 __dirty,标识是否处于待更新状态
// 初始 和 更新完毕,该值会被置为 false
// 推入队列时,标记为 true
if (
!updater.__dirty &&
(updater.__dirty = true) &&
updateQueue.push(updater) === 1//添加入队列
) {
// 异步化冲洗队列(微任务)
// 最终只执行一次冲洗
// 合并一次循环中多次 updater
new Promise().then(()=>{
if (updateQueue.length) {
updateQueue.sort()
let curUpdater = updateQueue.pop()
while (curUpdater) {
if (curUpdater.__dirty) {
// 当组件处于 待更新态 时,触发组件更新
// 如果该组件已经被更新完毕,则该状态为 false
// 则后续的更新均不再执行
curUpdater.__update()//更新组件 在组件更新完毕设置this.__dirty = false
//处理callback回调函数
const callbacks = curUpdater.__setStateCallbacks
let cbk
if (callbacks && callbacks.length) {
while (cbk = callbacks.shift()) cbk.call(curUpdater)
}
}
curUpdater = updateQueue.pop()
}
})
}
}
setState(partialState = {}, callback?) {
if (typeof partialState === 'function') {
partialState = partialState(this.state, this.props)
}
this.__nextState = {
...this.state,
...partialState,
}
// 缓存回调
callback && this.__setStateCallbacks.push(callback)
// 把组件自身先推入更新队列
enqueueUpdate(this)
}
- __update更新组件
diff虚拟DOM
重新渲染组件
__update(){
diff(oldVNode, newVNode);
render();
}
function diff(oldVNode, newVNode) {
if (isSameVNode(oldVNode, newVNode)) {
if (typeof oldVNode.type === 'function') {
// 组件节点
diffComponent(oldVNode, newVNode)
} else {
// 元素节点,
// 直接执行比对
diffVNode(oldVNode, newVNode)
}
} else {
// 新节点替换旧节点
...
}
}
// 组件比对
function diffComponent(oldCompVNode, newCompVNode) {
const { instance, vnode: oldVNode, elm } = oldCompVNode
const { props: nextProps } = newCompVNode
if (instance && oldVNode) {
instance.__dirty = false
// 更新状态和属性
instance.__nextProps = nextProps
if (!instance.__nextState) instance.__nextState = instance.state
// 复用旧组件实例和元素
newCompVNode.instance = instance
newCompVNode.elm = elm
// 使用新属性、新状态,旧组件实例
// 重新生成 新虚拟DOM
const newVNode = initComponent(newCompVNode)
// 递归触发 diff
diff(oldVNode, newVNode)
}
}
由于更新队列为异步的,因此当多次连续调用 setState 时,组件的状态会被 同步合并,待全部完成后,才会进入更新队列的冲洗并最终只执行一次组件更新
本文参考:https://juejin.im/post/5e65a258f265da57133b37cc#heading-7