第205集:Vue 3生命周期实现原理深度解析
概述
在本集中,我们将深入剖析Vue 3组件生命周期钩子的实现原理。生命周期钩子是Vue组件从创建到销毁过程中的关键节点,允许开发者在特定阶段执行自定义逻辑。理解生命周期的实现机制对于掌握Vue 3组件系统至关重要。
生命周期核心架构
Vue 3生命周期系统主要包含以下核心模块:
- 生命周期枚举:定义生命周期钩子类型
- 生命周期注册:注册组件生命周期钩子
- 生命周期调用:在特定阶段调用生命周期钩子
- 生命周期管理:管理生命周期钩子的执行顺序和上下文
源码目录结构
Vue 3生命周期系统的源码主要位于packages/runtime-core/src/目录下:
packages/runtime-core/src/
├── enums.ts # 生命周期枚举定义
├── component.ts # 组件核心实现,包含生命周期管理
├── lifecycle.ts # 生命周期钩子相关实现
└── renderer.ts # 渲染器,调用生命周期钩子核心源码解析
1. 生命周期枚举
生命周期钩子类型定义在runtime-core/src/enums.ts中:
// packages/runtime-core/src/enums.ts
/**
* 生命周期钩子枚举
*/
export const enum LifecycleHooks {
BEFORE_CREATE = 0, // 组件创建前
CREATED = 1, // 组件创建后
BEFORE_MOUNT = 2, // 组件挂载前
MOUNTED = 3, // 组件挂载后
BEFORE_UPDATE = 4, // 组件更新前
UPDATED = 5, // 组件更新后
BEFORE_UNMOUNT = 6, // 组件卸载前
UNMOUNTED = 7, // 组件卸载后
DEACTIVATED = 8, // 组件失活时(KeepAlive)
ACTIVATED = 9, // 组件激活时(KeepAlive)
RENDER_TRIGGERED = 10, // 渲染触发时(开发模式)
RENDER_TRACKED = 11, // 渲染追踪时(开发模式)
ERROR_CAPTURED = 12, // 捕获错误时
SERVER_PREFETCH = 13 // 服务端预取时
}2. 组件实例中的生命周期管理
组件实例包含生命周期钩子的管理结构,定义在runtime-core/src/component.ts中:
// packages/runtime-core/src/component.ts
/**
* 组件内部实例接口
*/
export interface ComponentInternalInstance {
// ... 其他属性
// 生命周期钩子数组
// 每个索引对应一个生命周期钩子类型
// 每个生命周期钩子类型对应一个钩子函数数组
hooks: (Function[] | null)[]
// ... 其他属性
}3. 注册生命周期钩子
registerHook函数负责注册组件生命周期钩子,定义在runtime-core/src/lifecycle.ts中:
// packages/runtime-core/src/lifecycle.ts
/**
* 注册生命周期钩子
* @param instance 组件实例
* @param type 生命周期类型
* @param hook 钩子函数
* @param target 目标对象(可选)
*/
export function registerHook(
instance: ComponentInternalInstance,
type: LifecycleHooks,
hook: Function & { __weh?: Function },
target: ComponentInternalInstance | null = null
) {
// 获取目标实例
const rawTarget = target || instance
// 获取生命周期钩子数组
const hooks = rawTarget.hooks || (rawTarget.hooks = [])
// 获取指定类型的钩子数组
const current = hooks[type] || (hooks[type] = [])
// 如果是setup中注册的钩子,需要特殊处理
const wrappedHook = hook.__weh || (hook.__weh = (...args: unknown[]) => {
// 确保只在组件激活时调用
if (instance.isUnmounted) {
return
}
// 设置当前实例为激活实例
setCurrentInstance(instance)
// 调用钩子函数
const res = hook.call(instance.proxy, ...args)
// 重置当前实例
setCurrentInstance(null)
return res
})
// 添加到钩子数组
current.push(wrappedHook)
}4. 调用生命周期钩子
callHook函数负责调用指定类型的生命周期钩子,定义在runtime-core/src/lifecycle.ts中:
// packages/runtime-core/src/lifecycle.ts
/**
* 调用生命周期钩子
* @param instance 组件实例
* @param type 生命周期类型
* @param args 传递给钩子的参数
*/
export function callHook(
instance: ComponentInternalInstance,
type: LifecycleHooks,
...args: unknown[]
) {
// 确保只在组件激活时调用(除了UNMOUNTED)
if (type !== LifecycleHooks.UNMOUNTED && instance.isUnmounted) {
return
}
// 获取指定类型的钩子数组
const hooks = instance.hooks[type]
if (!hooks) {
return
}
// 调用所有钩子函数
for (let i = 0; i < hooks.length; i++) {
try {
// 调用钩子函数
const hook = hooks[i]
setCurrentInstance(instance)
hook(...args)
} catch (e: any) {
// 捕获并处理错误
handleError(e, instance, ErrorCodes.LIFECYCLE_HOOK)
} finally {
// 重置当前实例
setCurrentInstance(null)
}
}
}5. 组件创建阶段的生命周期
组件创建阶段会调用BEFORE_CREATE和CREATED钩子,这一过程发生在setupComponent函数中:
// packages/runtime-core/src/component.ts
/**
* 设置有状态组件
* @param instance 组件实例
*/
function setupStatefulComponent(instance: ComponentInternalInstance) {
const Component = instance.type as ComponentOptions
// 调用BEFORE_CREATE钩子
callHook(instance, LifecycleHooks.BEFORE_CREATE)
// 处理inject
if (Component.inject) {
resolveInjections(instance)
}
// 调用setup函数
if (Component.setup) {
// ... 调用setup函数的逻辑
}
// 初始化组件状态
if (Component.data) {
initData(instance)
}
if (Component.computed) {
initComputed(instance)
}
if (Component.watch) {
initWatch(instance, Component.watch)
}
// 处理provide
if (Component.provide) {
initProvide(instance, Component.provide)
}
// 调用CREATED钩子
callHook(instance, LifecycleHooks.CREATED)
}6. 组件挂载阶段的生命周期
组件挂载阶段会调用BEFORE_MOUNT和MOUNTED钩子,这一过程发生在渲染器的setupRenderEffect函数中:
// packages/runtime-core/src/renderer.ts
/**
* 设置渲染效果
* @param instance 组件实例
* @param initialVNode 初始VNode
* @param container 容器
* @param anchor 锚点
* @param parentSuspense 父悬念
* @param namespace 命名空间
* @param slotScopeIds 插槽作用域id
* @param optimized 是否优化
*/
const setupRenderEffect = (instance: ComponentInternalInstance, initialVNode: VNode, container: RendererElement, anchor: RendererNode | null, parentSuspense: SuspenseBoundary | null, namespace: RendererNamespace | null, slotScopeIds: string[] | null, optimized: boolean) => {
// 创建渲染效果
instance.update = effect(function componentEffect() {
if (!instance.isMounted) {
// 首次挂载
// 调用BEFORE_MOUNT钩子
callHook(instance, LifecycleHooks.BEFORE_MOUNT)
// ... 渲染和挂载逻辑
// 调用MOUNTED钩子
instance.isMounted = true
callHook(instance, LifecycleHooks.MOUNTED)
} else {
// ... 更新逻辑
}
}, {
scheduler: queueJob,
onTrack: instance.rtc && instance.rtc.onTrack,
onTrigger: instance.rtc && instance.rtc.onTrigger
})
}7. 组件更新阶段的生命周期
组件更新阶段会调用BEFORE_UPDATE和UPDATED钩子,这一过程发生在渲染器的componentEffect函数中:
// packages/runtime-core/src/renderer.ts
const setupRenderEffect = (instance: ComponentInternalInstance, initialVNode: VNode, container: RendererElement, anchor: RendererNode | null, parentSuspense: SuspenseBoundary | null, namespace: RendererNamespace | null, slotScopeIds: string[] | null, optimized: boolean) => {
// 创建渲染效果
instance.update = effect(function componentEffect() {
if (!instance.isMounted) {
// ... 挂载逻辑
} else {
// 更新组件
// 调用BEFORE_UPDATE钩子
callHook(instance, LifecycleHooks.BEFORE_UPDATE)
// ... 生成新VNode和diff逻辑
// 调用UPDATED钩子
callHook(instance, LifecycleHooks.UPDATED)
}
}, {
scheduler: queueJob,
onTrack: instance.rtc && instance.rtc.onTrack,
onTrigger: instance.rtc && instance.rtc.onTrigger
})
}8. 组件卸载阶段的生命周期
组件卸载阶段会调用BEFORE_UNMOUNT和UNMOUNTED钩子,这一过程发生在渲染器的unmountComponent函数中:
// packages/runtime-core/src/renderer.ts
/**
* 卸载组件
* @param instance 组件实例
* @param parentSuspense 父悬念
* @param doRemove 是否移除DOM
*/
const unmountComponent = (instance: ComponentInternalInstance, parentSuspense: SuspenseBoundary | null, doRemove?: boolean) => {
// 调用BEFORE_UNMOUNT钩子
if (instance.isMounted) {
callHook(instance, LifecycleHooks.BEFORE_UNMOUNT)
}
// ... 卸载子树和清理资源逻辑
// 调用UNMOUNTED钩子
instance.isUnmounted = true
callHook(instance, LifecycleHooks.UNMOUNTED)
// 清理组件资源
cleanupComponent(instance)
}9. KeepAlive相关生命周期
KeepAlive组件会调用DEACTIVATED和ACTIVATED钩子,这一过程发生在KeepAlive.ts中:
// packages/runtime-core/src/components/KeepAlive.ts
/**
* 卸载组件(用于KeepAlive)
* @param vnode 组件VNode
* @param parentComponent 父组件
* @param parentSuspense 父悬念
* @param doRemove 是否移除DOM
*/
function unmount(vnode: VNode, parentComponent: ComponentInternalInstance | null, parentSuspense: SuspenseBoundary | null, doRemove?: boolean) {
const instance = vnode.component!
// 调用DEACTIVATED钩子
callHook(instance, LifecycleHooks.DEACTIVATED)
// ... 其他卸载逻辑
}
/**
* 挂载组件(用于KeepAlive)
* @param vnode 组件VNode
* @param container 容器
* @param anchor 锚点
* @param parentComponent 父组件
* @param parentSuspense 父悬念
* @param namespace 命名空间
* @param slotScopeIds 插槽作用域id
* @param optimized 是否优化
*/
function mount(vnode: VNode, container: RendererElement, anchor: RendererNode | null, parentComponent: ComponentInternalInstance | null, parentSuspense: SuspenseBoundary | null, namespace: RendererNamespace | null, slotScopeIds: string[] | null, optimized: boolean) {
const instance = vnode.component!
// ... 其他挂载逻辑
// 调用ACTIVATED钩子
callHook(instance, LifecycleHooks.ACTIVATED)
}10. 开发模式特有的生命周期
在开发模式下,Vue 3还提供了RENDER_TRACKED和RENDER_TRIGGERED钩子,用于调试响应式系统:
// packages/runtime-core/src/component.ts
/**
* 设置组件运行时上下文
* @param instance 组件实例
* @param appContext 应用上下文
*/
export function setRuntimeContext(instance: ComponentInternalInstance, appContext: AppContext) {
instance.rtc = appContext.config.performance && isDev
? {
onTrack: (e: DebuggerEvent) => {
callHook(instance, LifecycleHooks.RENDER_TRACKED, e)
},
onTrigger: (e: DebuggerEvent) => {
callHook(instance, LifecycleHooks.RENDER_TRIGGERED, e)
}
}
: null
}生命周期执行顺序
Vue 3组件生命周期钩子的执行顺序如下:
组件创建阶段
BEFORE_CREATE:组件实例创建前调用CREATED:组件实例创建后调用
组件挂载阶段
BEFORE_MOUNT:组件挂载到DOM前调用MOUNTED:组件挂载到DOM后调用
组件更新阶段
BEFORE_UPDATE:组件更新前调用UPDATED:组件更新后调用
组件卸载阶段
BEFORE_UNMOUNT:组件卸载前调用UNMOUNTED:组件卸载后调用
KeepAlive特有阶段
DEACTIVATED:组件失活时调用ACTIVATED:组件激活时调用
开发模式特有阶段
RENDER_TRACKED:响应式依赖被追踪时调用RENDER_TRIGGERED:响应式依赖被触发时调用
生命周期钩子的使用
在Vue 3中,生命周期钩子可以通过以下方式使用:
1. 选项式API
const App = {
beforeCreate() {
console.log('beforeCreate called')
},
created() {
console.log('created called')
},
beforeMount() {
console.log('beforeMount called')
},
mounted() {
console.log('mounted called')
},
beforeUpdate() {
console.log('beforeUpdate called')
},
updated() {
console.log('updated called')
},
beforeUnmount() {
console.log('beforeUnmount called')
},
unmounted() {
console.log('unmounted called')
}
}2. Composition API
import { onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted } from 'vue'
const App = {
setup() {
onBeforeMount(() => {
console.log('onBeforeMount called')
})
onMounted(() => {
console.log('onMounted called')
})
onBeforeUpdate(() => {
console.log('onBeforeUpdate called')
})
onUpdated(() => {
console.log('onUpdated called')
})
onBeforeUnmount(() => {
console.log('onBeforeUnmount called')
})
onUnmounted(() => {
console.log('onUnmounted called')
})
}
}生命周期钩子的实现细节
1. 钩子函数的上下文
生命周期钩子函数的上下文(this)指向组件实例的代理对象(instance.proxy),这是一个代理对象,用于访问组件的属性和方法。
2. 钩子函数的执行顺序
同类型的生命周期钩子会按照注册顺序执行,先注册的先执行。
3. 子组件与父组件的钩子执行顺序
- 挂载阶段:父组件
beforeMount→ 子组件beforeMount→ 子组件mounted→ 父组件mounted - 更新阶段:父组件
beforeUpdate→ 子组件beforeUpdate→ 子组件updated→ 父组件updated - 卸载阶段:父组件
beforeUnmount→ 子组件beforeUnmount→ 子组件unmounted→ 父组件unmounted
4. 异步组件的生命周期
异步组件的生命周期钩子会在组件加载完成后调用,这一点需要特别注意。
性能优化建议
- 避免在生命周期钩子中做过多计算:特别是在
updated钩子中,避免执行复杂计算,以免影响性能 - 合理使用生命周期钩子:只在必要的阶段执行必要的逻辑
- 清理资源:在
beforeUnmount或unmounted钩子中清理定时器、事件监听器等资源 - 使用Composition API的生命周期:Composition API的生命周期钩子可以更精确地控制执行时机
总结
本集深入剖析了Vue 3组件生命周期钩子的实现原理,包括生命周期枚举、注册、调用和管理等核心模块。理解生命周期的实现机制对于掌握Vue 3组件系统至关重要。
Vue 3的生命周期系统通过一系列优化,如Composition API的生命周期钩子、更精确的执行时机等,显著提升了组件的性能和开发体验。通过合理使用生命周期钩子,我们可以写出高效、可靠的Vue 3组件。
在后续的源码解析系列中,我们将继续深入探讨Vue 3的其他核心模块,包括指令系统源码分析、插槽实现机制等。