- 发布于
Vue3的组件的更新过程
- Authors
- Name
- 田中原
Vue3的组件的更新过程
目录
渲染的入口
接着上次挂载组件往下看packages/runtime-core/src/renderer.ts
的mountComponent
组件渲染实际上是调用了setupRenderEffect
setupRenderEffect
主要逻辑
- 声明了一个组件更新函数,
- 新
vnode
的创建 - 新旧
vnode
通过patch
- 新
- 处理副作用,和
SchedulerJob
- 通过
new ReactiveEffect
创建effect
实例, - 实例的
update
函数加入effect.run
- 通过
- 最后调用
update
patch
中的逻辑
节点更新在const patch: PatchFn = (
n1,
n2,
container,
anchor = null,
parentComponent = null,
parentSuspense = null,
namespace = undefined,
slotScopeIds = null,
optimized = __DEV__ && isHmrUpdating ? false : !!n2.dynamicChildren,
) => {
if (n1 === n2) {
// 新旧相同直接返回
return
}
// patching & not same type, unmount old tree
if (n1 && !isSameVNodeType(n1, n2)) {
// n1有值是更新,并且不是相同类型,卸载旧树。如果更新不走这里就该走diff的流程
anchor = getNextHostNode(n1)
unmount(n1, parentComponent, parentSuspense, true)
// n1改为null,后续会重新挂载
n1 = null
}
if (n2.patchFlag === PatchFlags.BAIL) {
optimized = false
n2.dynamicChildren = null
}
...
if (shapeFlag & ShapeFlags.ELEMENT) {
// 处理成真实的元素
processElement()
} else if (shapeFlag & ShapeFlags.COMPONENT) {
// 对组件处理
processComponent()
}
更新时对组件的处理
processComponent
⇒ updateComponent
主要判断当前组件的子组件是否需要更新 如果子组件也需要更新(子组件自己状态变化了),会从更新队列中删除子组件,手动更新子
/**
* @description 判断子组件是否需要更新
* 如果需要则递归执行子组件的副作用渲染函数更新
* 否则只更新vnode属性,让子组件实例保留对组件vnode的引用
* 用于子组件自身数据变化重新渲染时,渲染函数能拿到父组件的vnode
*/
const updateComponent = (n1: VNode, n2: VNode, optimized: boolean) => {
const instance = (n2.component = n1.component)!
// 根据新旧子组件的VNode,判断是否需要更新子组件
if (shouldUpdateComponent(n1, n2, optimized)) {
if (
__FEATURE_SUSPENSE__ &&
instance.asyncDep &&
!instance.asyncResolved
) {
// async & still pending(suspense未resolved) - 只更新props和slots,因为组件的渲染响应效果尚未设置
// 更新组件上的信息,比如props、slots等
updateComponentPreRender(instance, n2, optimized)
return
} else {
instance.next = n2
// 如果子组件也在队列中,删除它,以避免在同一次刷新中多次更新同一个子组件。
// 避免子组件由于自身数据变化导致的重复更新。等于是把子组件的更新任务从队列中移除,手动更新了子组件
invalidateJob(instance.update)
// instance.update是响应式effect
instance.effect.dirty = true
// 执行子组件的副作用渲染函数,主动触发子组件的更新
instance.update()
}
} else {
// 不需要更新,只需复制属性,
n2.el = n1.el
instance.vnode = n2
}
}
对元素的处理
patchElement
⇒ patchChildren
最终的dom操作
- 通过
patchChildren
处理子节点,主要是文本、数组、或无子节点 - 通过
patchProps
处理dom上的属性
/**
*
* @description 主要做两件事
* 1. patchProps处理props
* 2. patchChildren处理子节点
*/
const patchElement = (
n1: VNode,
n2: VNode,
parentComponent: ComponentInternalInstance | null,
parentSuspense: SuspenseBoundary | null,
namespace: ElementNamespace,
slotScopeIds: string[] | null,
optimized: boolean,
) => {
const el = (n2.el = n1.el!)
let { patchFlag, dynamicChildren, dirs } = n2
patchFlag |= n1.patchFlag & PatchFlags.FULL_PROPS
const oldProps = n1.props || EMPTY_OBJ
const newProps = n2.props || EMPTY_OBJ
let vnodeHook: VNodeHook | undefined | null
// 有父组件的话,禁用递归
parentComponent && toggleRecurse(parentComponent, false)
if ((vnodeHook = newProps.onVnodeBeforeUpdate)) {
// 有vnodeHook的话,调用vVnode的onVnodeBeforeUpdate
invokeVNodeHook(vnodeHook, parentComponent, n2, n1)
}
if (dirs) {
// dirs,调用的beforeUpdate生命周期
invokeDirectiveHook(n2, n1, parentComponent, 'beforeUpdate')
}
parentComponent && toggleRecurse(parentComponent, true)
...
// 没法优化、全量diff
patchChildren()
... 省略成吨的逻辑判断
patchProps()
if ((vnodeHook = newProps.onVnodeUpdated) || dirs) {
// dom处理完毕调用updated生命周期
queuePostRenderEffect(() => {
vnodeHook && invokeVNodeHook(vnodeHook, parentComponent, n2, n1)
dirs && invokeDirectiveHook(n2, n1, parentComponent, 'updated')
}, parentSuspense)
}
}