component节点的处理
前言
上一节我们分析了diff算法对于不同类型节点的处理,本节我们就来分析最重要的component节点的处理。对于component节点的处理,要么mountComponent,要么updateComponent。
const processComponent = (
n1: VNode | null,
n2: VNode,
container: RendererElement,
anchor: RendererNode | null,
parentComponent: ComponentInternalInstance | null,
parentSuspense: SuspenseBoundary | null,
namespace: ElementNamespace,
slotScopeIds: string[] | null,
optimized: boolean,
) => {
n2.slotScopeIds = slotScopeIds
if (n1 == null) {
if (n2.shapeFlag & ShapeFlags.COMPONENT_KEPT_ALIVE) {
;(parentComponent!.ctx as KeepAliveContext).activate(
n2,
container,
anchor,
namespace,
optimized,
)
} else {
mountComponent(
n2,
container,
anchor,
parentComponent,
parentSuspense,
namespace,
optimized,
)
}
} else {
updateComponent(n1, n2, optimized)
}
}
mountComponent 函数
主要流程
- 创建组件实例
// 兼容Vue 2.x的情况
const compatMountInstance =
__COMPAT__ && initialVNode.isCompatRoot && initialVNode.component
const instance: ComponentInternalInstance =
compatMountInstance ||
(initialVNode.component = createComponentInstance(
initialVNode,
parentComponent,
parentSuspense,
))
- 开发环境处理
if (__DEV__ && instance.type.__hmrId) {
registerHMR(instance)
}
if (__DEV__) {
pushWarningContext(initialVNode)
startMeasure(instance, `mount`)
}
- keep-alive处理
// 注入渲染器内部方法到keep-alive组件
if (isKeepAlive(initialVNode)) {
;(instance.ctx as KeepAliveContext).renderer = internals
}
- 设置组件
// 非兼容模式下初始化组件
if (!(__COMPAT__ && compatMountInstance)) {
if (__DEV__) {
startMeasure(instance, `init`)
}
setupComponent(instance, false, optimized)
if (__DEV__) {
endMeasure(instance, `init`)
}
}
- 异步组件处理
if (__FEATURE_SUSPENSE__ && instance.asyncDep) {
// 避免HMR更新时的hydration
if (__DEV__ && isHmrUpdating) initialVNode.el = null
// 注册异步依赖
parentSuspense &&
parentSuspense.registerDep(instance, setupRenderEffect, optimized)
// 创建占位符
if (!initialVNode.el) {
const placeholder = (instance.subTree = createVNode(Comment))
processCommentNode(null, placeholder, container!, anchor)
}
} else {
// 设置渲染效果
setupRenderEffect(
instance,
initialVNode,
container,
anchor,
parentSuspense,
namespace,
optimized,
)
}
关键步骤解析
-
创建组件实例
- 首先检查是否是Vue 2.x兼容模式下预创建的实例
- 如果不是,则通过
createComponentInstance创建新实例 - 实例中包含了组件的各种上下文信息
-
开发环境处理
- 注册HMR(热更新)相关功能
- 设置警告上下文
- 添加性能测量
-
keep-alive处理
- 检查是否是keep-alive组件
- 如果是,注入渲染器内部方法到组件上下文
-
设置组件(setupComponent)
- 初始化props和slots
- 执行组件的setup函数
- 处理组件的状态和生命周期
- 在开发环境下进行性能测量
-
异步组件处理
- 检查组件是否有异步依赖(asyncDep)
- 如果有异步依赖:
- 在开发环境下处理HMR更新
- 注册到父Suspense组件
- 创建注释节点作为占位符
- 如果没有异步依赖:
- 直接设置渲染效果
updateComponent 函数
updateComponent 函数负责处理组件的更新逻辑。它接收以下参数:
- n1: 旧的虚拟节点
- n2: 新的虚拟节点
- optimized: 是否优化
详细流程
const updateComponent = (n1: VNode, n2: VNode, optimized: boolean) => {
// 获取组件实例并保持引用一致性
const instance = (n2.component = n1.component)!
// 判断是否需要更新
if (shouldUpdateComponent(n1, n2, optimized)) {
// 处理异步组件的特殊情况
if (__FEATURE_SUSPENSE__ && instance.asyncDep && !instance.asyncResolved) {
// 异步组件还未解析完成,只更新props和slots
if (__DEV__) {
pushWarningContext(n2)
}
updateComponentPreRender(instance, n2, optimized)
if (__DEV__) {
popWarningContext()
}
return
} else {
// 正常更新流程
instance.next = n2 // 设置新的虚拟节点
instance.update() // 触发组件更新
}
} else {
// 不需要更新,直接复制属性
n2.el = n1.el // 复用DOM元素
instance.vnode = n2 // 更新vnode引用
}
}
关键步骤解析
-
更新判断
- 通过
shouldUpdateComponent判断是否需要更新 - 该函数会检查:
- props是否发生变化
- children是否发生变化
- 组件是否有动态插槽
- 是否有指令变化
- 通过
-
异步组件处理
- 检查组件是否是未解析的异步组件
- 如果是未解析的异步组件:
- 在开发环境下设置警告上下文
- 只更新props和slots
- 不触发完整的组件更新流程
-
正常更新流程
- 设置新的虚拟节点(next)
- 调用实例的update方法触发更新
- update方法是一个响应式effect,会触发组件的重新渲染
-
优化处理
- 当不需要更新时:
- 直接复用原有的DOM元素(el)
- 更新组件实例的vnode引用
- 这种优化可以避免不必要的DOM操作
- 当不需要更新时:
两个函数的关系
-
调用时机
mountComponent: 组件首次渲染时调用updateComponent: 组件数据变化需要更新时调用
-
职责区分
mountComponent:- 负责组件的初始化
- 创建组件实例
- 设置响应式系统
- 首次渲染DOM
updateComponent:- 负责组件的更新
- 判断是否需要更新
- 处理异步组件更新
- 触发重新渲染
总结
本节,我们分析了Vue组件系统中负责组件挂载和更新的两个核心函数。在这两个函数执行的过程中又调用了一些重要的内部函数,待进一步分析:
setupComponent: 负责组件的初始化设置setupRenderEffect: 创建组件的渲染副作用shouldUpdateComponent: 判断组件是否需要更新updateComponentPreRender: 处理异步组件的预渲染更新
下一节我们分析setupComponent函数和setupRenderEffect函数。