Appearance
第 12 章 生命周期与调度
本章要点
- 组件生命周期的完整图谱:从 setup 到 unmounted,每个钩子的触发时机和内部实现
- 生命周期钩子的注册机制:injectHook 如何将钩子函数绑定到组件实例
- 调度器的核心设计:异步批量更新的队列模型和 flush 时机
- nextTick 的实现本质:为什么它能保证在 DOM 更新后执行
- 三种队列的优先级:pre 队列、queue 队列、post 队列的协作关系
- Suspense 对生命周期的影响:异步组件如何改变钩子触发顺序
- 调度器的错误处理与递归保护机制
在上一章中,我们深入了虚拟 DOM 和 Diff 算法——这是 Vue 渲染管线的"引擎"。但引擎不能随意启动,它需要一个精密的调度系统来协调"何时更新"、"以什么顺序更新"、"更新完之后做什么"。
同时,每个组件都有自己的"生命"——从诞生到消亡,在不同的阶段,开发者需要介入执行特定的逻辑。这就是生命周期系统的使命。
调度器和生命周期看似独立,实则紧密耦合。生命周期钩子的触发时机由调度器控制,而调度器的行为又受组件状态(是否挂载、是否激活)的制约。本章将一并剖析。
12.1 组件生命周期全景
生命周期的完整流程
Composition API 中的生命周期
在 Vue 3 的 Composition API 中,生命周期钩子通过 onXxx 函数注册:
typescript
import {
onBeforeMount,
onMounted,
onBeforeUpdate,
onUpdated,
onBeforeUnmount,
onUnmounted,
onActivated,
onDeactivated,
onErrorCaptured,
onRenderTracked,
onRenderTriggered
} from 'vue'
export default {
setup() {
onBeforeMount(() => {
console.log('DOM 即将创建')
})
onMounted(() => {
console.log('DOM 已创建,可以访问 this.$el')
})
onBeforeUpdate(() => {
console.log('DOM 即将更新')
})
onUpdated(() => {
console.log('DOM 已更新')
})
onBeforeUnmount(() => {
console.log('组件即将卸载')
})
onUnmounted(() => {
console.log('组件已卸载,所有副作用已清理')
})
}
}注意:setup 本身就是在 beforeCreate 和 created 之间执行的,所以 Composition API 中没有这两个钩子的对应函数——setup 就是它们的替代品。
12.2 钩子注册机制:injectHook
所有 onXxx 函数内部都调用同一个底层函数 injectHook:
typescript
// packages/runtime-core/src/apiLifecycle.ts
export const onBeforeMount = createHook(LifecycleHooks.BEFORE_MOUNT)
export const onMounted = createHook(LifecycleHooks.MOUNTED)
export const onBeforeUpdate = createHook(LifecycleHooks.BEFORE_UPDATE)
export const onUpdated = createHook(LifecycleHooks.UPDATED)
export const onBeforeUnmount = createHook(LifecycleHooks.BEFORE_UNMOUNT)
export const onUnmounted = createHook(LifecycleHooks.UNMOUNTED)
// createHook 只是一层柯里化
export const createHook = <T extends Function = () => any>(
lifecycle: LifecycleHooks
) => {
return (hook: T, target: ComponentInternalInstance | null = currentInstance) =>
injectHook(lifecycle, (...args: unknown[]) => hook(...args), target)
}injectHook 的核心逻辑:
typescript
// packages/runtime-core/src/apiLifecycle.ts
export function injectHook(
type: LifecycleHooks,
hook: Function & { __weh?: Function },
target: ComponentInternalInstance | null = currentInstance,
prepend: boolean = false
): Function | undefined {
if (target) {
// 获取或创建钩子数组
// 组件实例上用简写存储:bm=beforeMount, m=mounted 等
const hooks = target[type] || (target[type] = [])
// 包装钩子函数,确保调用时 currentInstance 正确
const wrappedHook =
hook.__weh ||
(hook.__weh = (...args: unknown[]) => {
if (target.isUnmounted) {
return
}
// 暂停追踪,防止钩子中的响应式访问被错误收集
pauseTracking()
// 设置当前实例,确保钩子内部能访问组件上下文
const reset = setCurrentInstance(target)
// 调用钩子,捕获错误
const res = callWithAsyncErrorHandling(hook, target, type, args)
reset()
resetTracking()
return res
})
if (prepend) {
hooks.unshift(wrappedHook)
} else {
hooks.push(wrappedHook)
}
return wrappedHook
}
}