渲染函数 setupRenderEffect
当setupComponent初始化组件结束后,进入同步渲染阶段
setupRenderEffect是Vue组件渲染的核心部分,负责创建响应式的副作用函数来处理组件的挂载和更新
其中ReactiveEffect 是 @vue/reactivity 核心类
const setupRenderEffect: SetupRenderEffectFn = (
instance,
initialVNode,
container,
anchor,
parentSuspense,
namespace,
optimized,
) => {
// 组件更新函数
const componentUpdateFn = () => {
...
}
// 创建响应式副作用函数
const effect = (instance.effect = // 创建响应式副作用
new ReactiveEffect(
componentUpdateFn, // 组件更新函数
NOOP, // 清理函数
() => queueJob(update), // 调度器
instance.scope // 作用域
)
)
const update: SchedulerJob = (instance.update = () => {
//执行副作用
if (effect.dirty) effect.run()
})
update()
}
组件更新函数componentUpdateFn
我们先看看componentUpdaten里都是在做了什么
componentUpdateFn的函数是Vue 3组件渲染的核心逻辑,处理了组件的挂载、更新、生命周期钩子、服务端渲染激活、异步组件加载以及开发者工具集成等多个方面。通过响应式系统和虚拟DOM的patch过程,实现了高效的组件渲染和更新
在挂载阶段(if (!instance.isMounted)),代码处理了beforeMount生命周期钩子,调用invokeArrayFns(bm)执行所有beforeMount钩子函数。接着处理onVnodeBeforeMount事件,如果存在则调用
接下来,检查是否需要 hydration(服务端渲染时的客户端激活)。如果有el且存在hydrateNode函数,则执行hydration逻辑。这里区分了异步组件包装器(isAsyncWrapperVNode)的情况,如果是异步组件,则等待其加载完成后再执行hydration。否则直接调用hydrateSubTree进行激活。对于非hydration情况,则执行常规的渲染和patch过程,生成子树并挂载到容器中
挂载完成后,处理mounted生命周期钩子和onVnodeMounted事件,同样考虑了兼容性钩子hook:mounted。此外,处理了KeepAlive组件的activated钩子,确保在适当时候触发。
在更新阶段(else部分),处理beforeUpdate钩子和onVnodeBeforeUpdate事件。然后重新渲染组件生成新的子树nextTree,并与旧的子树prevTree进行对比和patch。更新完成后,触发updated钩子和onVnodeUpdated事件,同样处理兼容性钩子hook:updated。
graph TD
A[组件更新入口] --> B{已挂载?}
B -->|否| C[挂载阶段]
B -->|是| D[更新阶段]
C --> C1[执行beforeMount钩子]
C1 --> C2[处理服务端渲染激活]
C2 --> C3[生成子树并patch]
C3 --> C4[触发mounted钩子]
D --> D1[执行beforeUpdate钩子]
D1 --> D2[重新渲染生成新子树]
D2 --> D3[patch新旧子树]
D3 --> D4[触发updated钩子]
代码如下
const componentUpdateFn = () => {
// 挂载/更新逻辑...
if (!instance.isMounted) {
// 1. beforeMount生命周期处理
if (bm) invokeArrayFns(bm)
// 2. 服务端渲染激活逻辑
if (el && hydrateNode) {
const hydrateSubTree = () => {
instance.subTree = renderComponentRoot(instance)
hydrateNode(el, instance.subTree, ...)
}
// 异步组件处理
if (isAsyncWrapperVNode) {
initialVNode.type.__asyncLoader().then(hydrateSubTree)
}
}
// 3. 常规渲染路径
const subTree = renderComponentRoot(instance)
patch(null, subTree, container, ...)
// 4. 挂载后处理 通过queuePostRenderEffect实现批量更新
queuePostRenderEffect(m) // mounted钩子
instance.isMounted = true
}else{
// 1. beforeUpdate处理
if (bu) invokeArrayFns(bu)
// 2. 重新渲染
const nextTree = renderComponentRoot(instance)
// 3. 差异比对
patch(prevTree, nextTree, ...)
// 4. 更新后处理
queuePostRenderEffect(u) // updated钩子
updateHOCHostEl() // 高阶组件处理
}
}
renderComponentRoot 生成组件的虚拟DOM树
函数的结构分为状态组件和函数式组件的处理。状态组件使用proxy或withProxy来调用render函数,而函数式组件则根据参数长度来处理
graph TD
A[开始渲染] --> B{组件类型}
B -->|状态组件| C[创建渲染代理]
B -->|函数式组件| D[处理函数式渲染]
C --> E[执行render函数]
D --> F[处理函数参数]
E --> G[属性合并处理]
F --> G
G --> H[错误边界处理]
H --> I[根节点处理]
I --> J[指令继承]
J --> K[过渡效果处理]
K --> L[返回最终VNode]
export function renderComponentRoot(
instance: ComponentInternalInstance,
): VNode {
// ...核心实现...
if (vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT) {
//状态组件
const proxyToUse = withProxy || proxy
result = normalizeVNode(
render!.call(thisProxy, ...)
)
}else{
//函数组件 根据参数长度自动判断渲染参数格式
result = normalizeVNode(
render.length > 1
? render(props, { attrs, slots, emit })
: render(props)
)
}
//属性合并机制
if (fallthroughAttrs && inheritAttrs !== false) {
root = cloneVNode(root, fallthroughAttrs, false, true)
}
}
renderComponentRoot生成组件的虚拟DOM树后,在执行patch函数进行新旧树diff比较
看完了组件更新函数componentUpdateFn后,我们大概知道了这是在组件挂在或者更新时,进行对应的VNode创建或者更新,进行diff比较,生成虚拟dom树
接着我们来看下ReactiveEffect副作用类的实现
ReactiveEffect类
ReactiveEffect是Vue响应式系统的核心类,用于管理副作用函数
类中有多个内部状态变量,如_dirtyLevel、_trackId等,这些用于跟踪副作用的依赖和状态。构造函数接收一个函数fn、触发器、调度器和作用域
run方法是执行副作用函数的地方。在运行前,会暂停跟踪,清理旧依赖,然后执行fn。完成后,恢复之前的跟踪状态
stop方法用于停止副作用,清理相关依赖并触发onStop回调
graph TD
A[创建Effect实例] --> B[管理依赖状态]
B --> C{状态判断}
C -->|Dirty| D[执行副作用函数]
C -->|NotDirty| E[跳过执行]
D --> F[收集新依赖]
F --> G[清理旧依赖]
G --> H[触发回调]
export class ReactiveEffect<T = any> {
// ...完整类实现...
_dirtyLevel = DirtyLevels.Dirty // 脏检查状态
_trackId = 0 // 依赖追踪版本号
_depsLength = 0 // 有效依赖数量
run() {
// 状态重置
this._dirtyLevel = DirtyLevels.NotDirty
// 激活状态检查
if (!this.active) return this.fn()
// 依赖追踪上下文管理
let lastShouldTrack = shouldTrack
let lastEffect = activeEffect
try {
// 执行前准备
shouldTrack = true
activeEffect = this
this._runnings++
preCleanupEffect(this)
// 执行副作用函数
return this.fn()
} finally {
// 执行后清理
postCleanupEffect(this)
this._runnings--
activeEffect = lastEffect
shouldTrack = lastShouldTrack
}
}
}
这样我们就大概知道了,vue在渲染组件的整个流程,如果是一次渲染,则创建组件实例,初始化组件,执行副作用函数
那么如果节点是组件类型,我们怎么判断是否要更新呢,vue3提供了shouldUpdateComponent关键决策函数函数
shouldUpdateComponent 关键决策函数
用来决定组件是否需要更新。
函数接收新旧VNode、优化标志等参数,返回一个布尔值
patchFlag分级检测 :
- 动态插槽 > 全属性 > 部分属性 的优先级判断
- 利用位运算快速判断更新类型
graph TD
A[开始判断] --> B{热更新场景?}
B -->|是| C[强制更新]
B -->|否| D{新VNode有指令/过渡?}
D -->|是| C
D -->|否| E{启用优化模式?}
E -->|是| F[基于patchFlag判断]
E -->|否| G[全量比较子节点和props]
F --> H{动态插槽?}
H -->|是| C
F --> I{全属性变化?}
I -->|是| J[比较所有props]
F --> K{部分属性变化?}
K -->|是| L[比较动态props]
G --> M{子节点不稳定?}
M -->|是| C
G --> N[比较新旧props]
export function shouldUpdateComponent(
prevVNode: VNode,
nextVNode: VNode,
optimized?: boolean,
): boolean {
// ...完整实现...
if (__DEV__ && (prevChildren || nextChildren) && isHmrUpdating) {
return true // 强制更新热替换组件
}
if (nextVNode.dirs || nextVNode.transition) {
return true // 运行时指令或过渡效果需要强制更新
}
if (optimized && patchFlag >= 0) {
// 动态插槽更新(如v-for产生的动态slot)
if (patchFlag & PatchFlags.DYNAMIC_SLOTS) return true
// 全属性更新判断
if (patchFlag & PatchFlags.FULL_PROPS) {
return hasPropsChanged(prevProps, nextProps!, emits)
}
// 部分属性更新判断
else if (patchFlag & PatchFlags.PROPS) {
for (const key of dynamicProps) {
if (props值变化 && 非emit事件) return true
}
}
}else{
if (prevChildren || nextChildren) {
if (!nextChildren?.$stable) return true // 子节点不稳定时强制更新
}
// 全量props比较
return hasPropsChanged(prevProps, nextProps, emits)
}
}
updateComponent 组件更新
当执行patch函数,发现是组件阶段且非首次的时候,要调用该函数
函数接收两个VNode(n1和n2)以及一个优化标志。首先,它把n1的组件实例赋给n2,确保实例复用。然后通过shouldUpdateComponent判断是否需要更新组件
如果不需要更新,直接复制属性;否则,处理异步组件的情况。对于异步组件,如果还在加载中,只更新props和slots,避免重复渲染。否则,将新VNode赋值给实例的next属性,并触发更新
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) {
updateComponentPreRender(instance, n2, optimized) // 仅更新props/slots
return
}else{
instance.next = n2 // 缓存新VNode
invalidateJob(instance.update) // 防止重复入队
instance.effect.dirty = true // 标记为脏状态
instance.update() // 触发响应式更新
}
} else {
// 跳过更新,直接同步属性
n2.el = n1.el
instance.vnode = n2
}
}
至此,整个render函数后续根据节点类型做不同处理的逻辑都看了一遍