Appearance
第5.1节:生命周期:各个生命周期钩子的执行时机与应用
生命周期钩子是Vue.js组件系统的核心特性之一,它们为开发者提供了在组件不同阶段执行代码的能力。Vue 3在保持与Vue 2兼容性的同时,通过组合式API提供了更加灵活和强大的生命周期管理机制。
5.1.1 生命周期钩子概览
生命周期枚举定义
在Vue 3源码中,所有生命周期钩子都通过枚举进行统一管理:
typescript
// core/packages/runtime-core/src/enums.ts
export const enum LifecycleHooks {
BEFORE_CREATE = 'bc',
CREATED = 'c',
BEFORE_MOUNT = 'bm',
MOUNTED = 'm',
BEFORE_UPDATE = 'bu',
UPDATED = 'u',
BEFORE_UNMOUNT = 'bum',
UNMOUNTED = 'um',
DEACTIVATED = 'da',
ACTIVATED = 'a',
RENDER_TRIGGERED = 'rtg',
RENDER_TRACKED = 'rtc',
ERROR_CAPTURED = 'ec',
SERVER_PREFETCH = 'sp'
}这种设计使得生命周期钩子的管理更加规范化,每个钩子都有对应的简短标识符,便于在组件实例中存储和调用。
组合式API生命周期函数
typescript
// core/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)
export const onActivated = createHook(LifecycleHooks.ACTIVATED)
export const onDeactivated = createHook(LifecycleHooks.DEACTIVATED)
export const onRenderTriggered = createHook(LifecycleHooks.RENDER_TRIGGERED)
export const onRenderTracked = createHook(LifecycleHooks.RENDER_TRACKED)5.1.2 生命周期钩子的注册机制
injectHook函数:钩子注册的核心
typescript
// core/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) {
const hooks = target[type] || (target[type] = [])
// 包装钩子函数,添加错误处理和调试信息
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
} else if (__DEV__) {
const apiName = toHandlerKey(ErrorTypeStrings[type].replace(/ hook$/, ''))
warn(
`${apiName} is called when there is no active component instance to be ` +
`associated with. ` +
`Lifecycle injection APIs can only be used during execution of setup().`
)
}
}createHook函数:生成钩子注册函数
typescript
export const createHook =
<T extends Function = () => any>(lifecycle: LifecycleHooks) =>
(hook: T, target: ComponentInternalInstance | null = currentInstance) =>
// post-create lifecycle registrations are noops during SSR (except for serverPrefetch)
(!isInSSRComponentSetup || lifecycle === LifecycleHooks.SERVER_PREFETCH) &&
injectHook(lifecycle, (...args: unknown[]) => hook(...args), target)5.1.3 组件挂载阶段的生命周期
beforeMount和mounted的执行时机
在组件挂载过程中,生命周期钩子的调用顺序严格按照以下流程:
typescript
// core/packages/runtime-core/src/renderer.ts - setupRenderEffect函数
const componentUpdateFn = () => {
if (!instance.isMounted) {
let vnodeHook: VNodeHook | null | undefined
const { el, props } = initialVNode
const { bm, m, parent } = instance
const isAsyncWrapperVNode = isAsyncWrapper(initialVNode)
toggleRecurse(instance, false)
// beforeMount hook
if (bm) {
invokeArrayFns(bm)
}
// onVnodeBeforeMount
if (
!isAsyncWrapperVNode &&
(vnodeHook = props && props.onVnodeBeforeMount)
) {
invokeVNodeHook(vnodeHook, parent, initialVNode)
}
toggleRecurse(instance, true)
if (el && hydrateNode) {
// 服务端渲染水合逻辑
} else {
// 客户端渲染
const subTree = (instance.subTree = renderComponentRoot(instance))
patch(
null,
subTree,
container,
anchor,
instance,
parentSuspense,
namespace
)
initialVNode.el = subTree.el
}
// mounted hook
if (m) {
queuePostRenderEffect(m, parentSuspense)
}
// onVnodeMounted
if (
!isAsyncWrapperVNode &&
(vnodeHook = props && props.onVnodeMounted)
) {
const scopedNext = initialVNode
queuePostRenderEffect(
() => invokeVNodeHook(vnodeHook!, parent, scopedNext),
parentSuspense
)
}
instance.isMounted = true
}
}关键执行顺序:
beforeMount- 同步执行,在DOM渲染之前- 组件渲染和DOM挂载
mounted- 异步执行,通过queuePostRenderEffect延迟到DOM更新后
5.1.4 组件更新阶段的生命周期
beforeUpdate和updated的执行机制
typescript
// 组件更新逻辑
else {
let { next, bu, u, parent, vnode } = instance
// beforeUpdate hook
if (bu) {
invokeArrayFns(bu)
}
// onVnodeBeforeUpdate
if ((vnodeHook = next.props && next.props.onVnodeBeforeUpdate)) {
invokeVNodeHook(vnodeHook, parent, next, vnode)
}
// 渲染新的子树
const nextTree = renderComponentRoot(instance)
const prevTree = instance.subTree
instance.subTree = nextTree
patch(
prevTree,
nextTree,
hostParentNode(prevTree.el!)!,
getNextHostNode(prevTree),
instance,
parentSuspense,
namespace
)
// updated hook
if (u) {
queuePostRenderEffect(u, parentSuspense)
}
// onVnodeUpdated
if ((vnodeHook = next.props && next.props.onVnodeUpdated)) {
queuePostRenderEffect(
() => invokeVNodeHook(vnodeHook!, parent, next!, vnode),
parentSuspense
)
}
}5.1.5 组件卸载阶段的生命周期
beforeUnmount和unmounted的执行机制
typescript
// core/packages/runtime-core/src/renderer.ts - unmountComponent函数
const unmountComponent = (
instance: ComponentInternalInstance,
parentSuspense: SuspenseBoundary | null,
doRemove?: boolean
) => {
const { bum, scope, job, subTree, um, m, a } = instance
invalidateMount(m)
invalidateMount(a)
// beforeUnmount hook - 同步执行
if (bum) {
invokeArrayFns(bum)
}
// 停止响应式作用域
scope.stop()
// 卸载子树
if (job) {
job.flags! |= SchedulerJobFlags.DISPOSED
unmount(subTree, instance, parentSuspense, doRemove)
}
// unmounted hook - 异步执行
if (um) {
queuePostRenderEffect(um, parentSuspense)
}
queuePostRenderEffect(() => {
instance.isUnmounted = true
}, parentSuspense)
}卸载阶段的关键特点:
beforeUnmount同步执行,此时组件实例和DOM都还存在- 响应式系统停止,清理副作用
- 卸载子组件和DOM节点
unmounted异步执行,确保所有清理工作完成
5.1.6 KeepAlive组件的特殊生命周期
activated和deactivated钩子
KeepAlive组件引入了两个特殊的生命周期钩子:
typescript
// core/packages/runtime-core/src/components/KeepAlive.ts
export function onActivated(
hook: Function,
target?: ComponentInternalInstance | null
): void {
registerKeepAliveHook(hook, LifecycleHooks.ACTIVATED, target)
}
export function onDeactivated(
hook: Function,
target?: ComponentInternalInstance | null
): void {
registerKeepAliveHook(hook, LifecycleHooks.DEACTIVATED, target)
}
function registerKeepAliveHook(
hook: Function & { __wdc?: Function },
type: LifecycleHooks,
target: ComponentInternalInstance | null = currentInstance
) {
// 缓存去激活分支检查包装器
const wrappedHook =
hook.__wdc ||
(hook.__wdc = () => {
// 只有当目标实例不在去激活分支中时才触发钩子
let current: ComponentInternalInstance | null = target
while (current) {
if (current.isDeactivated) {
return
}
current = current.parent
}
return hook()
})
injectHook(type, wrappedHook, target)
}KeepAlive的激活和去激活机制
typescript
// 激活组件
sharedContext.activate = (vnode, container, anchor, namespace, optimized) => {
const instance = vnode.component!
move(vnode, container, anchor, MoveType.ENTER, parentSuspense)
patch(
instance.vnode,
vnode,
container,
anchor,
instance,
parentSuspense,
namespace,
vnode.slotScopeIds,
optimized
)
queuePostRenderEffect(() => {
instance.isDeactivated = false
if (instance.a) {
invokeArrayFns(instance.a) // 调用activated钩子
}
}, parentSuspense)
}
// 去激活组件
sharedContext.deactivate = (vnode: VNode) => {
const instance = vnode.component!
move(vnode, storageContainer, null, MoveType.LEAVE, parentSuspense)
queuePostRenderEffect(() => {
if (instance.da) {
invokeArrayFns(instance.da) // 调用deactivated钩子
}
instance.isDeactivated = true
}, parentSuspense)
}5.1.7 错误捕获生命周期
onErrorCaptured的实现
typescript
// core/packages/runtime-core/src/apiLifecycle.ts
export type ErrorCapturedHook<TError = unknown> = (
err: TError,
instance: ComponentPublicInstance | null,
info: string
) => boolean | void
export function onErrorCaptured<TError = Error>(
hook: ErrorCapturedHook<TError>,
target: ComponentInternalInstance | null = currentInstance
) {
injectHook(LifecycleHooks.ERROR_CAPTURED, hook as any, target)
}错误捕获钩子在错误处理系统中发挥重要作用,能够捕获子组件中的错误并进行处理。
5.1.8 getCurrentInstance:获取当前组件实例
实现机制
typescript
// core/packages/runtime-core/src/component.ts
let currentInstance: ComponentInternalInstance | null = null
export const getCurrentInstance: () => ComponentInternalInstance | null = () =>
currentInstance || currentRenderingInstance
export const setCurrentInstance = (instance: ComponentInternalInstance) => {
const prev = currentInstance
currentInstance = instance
instance.scope.on()
return () => {
instance.scope.off()
currentInstance = prev
}
}getCurrentInstance只能在setup函数或生命周期钩子中调用,这是因为:
- 只有在这些阶段,
currentInstance才会被正确设置 - 确保了组件实例的正确性和安全性
- 避免了在错误的上下文中访问组件实例
5.1.9 父子组件生命周期执行顺序
挂载阶段的执行顺序
父组件 beforeMount
子组件 beforeMount
子组件 mounted
父组件 mounted更新阶段的执行顺序
父组件 beforeUpdate
子组件 beforeUpdate
子组件 updated
父组件 updated卸载阶段的执行顺序
父组件 beforeUnmount
子组件 beforeUnmount
子组件 unmounted
父组件 unmounted这种执行顺序确保了:
- 父组件总是在子组件之前开始生命周期
- 子组件总是在父组件之前完成生命周期
- 保证了组件树的一致性和数据流的正确性
5.1.10 Suspense中的生命周期
异步组件的特殊处理
Suspense组件为异步组件提供了特殊的生命周期处理机制:
typescript
// core/packages/runtime-core/src/components/Suspense.ts
export function queueEffectWithSuspense(
fn: Function | Function[],
suspense: SuspenseBoundary | null
): void {
if (suspense && suspense.pendingBranch) {
if (isArray(fn)) {
suspense.effects.push(...fn)
} else {
suspense.effects.push(fn)
}
} else {
queuePostFlushCb(fn)
}
}在Suspense环境中:
- 生命周期钩子会被延迟到异步组件解析完成
- 确保了异步组件的生命周期与同步组件保持一致
- 提供了更好的用户体验和错误处理
5.1.11 最佳实践与应用场景
1. 数据获取
javascript
// 推荐:在mounted中获取数据
import { onMounted, ref } from 'vue'
export default {
setup() {
const data = ref(null)
const loading = ref(true)
onMounted(async () => {
try {
data.value = await fetchData()
} finally {
loading.value = false
}
})
return { data, loading }
}
}2. 事件监听器管理
javascript
import { onMounted, onUnmounted } from 'vue'
export default {
setup() {
const handleResize = () => {
// 处理窗口大小变化
}
onMounted(() => {
window.addEventListener('resize', handleResize)
})
onUnmounted(() => {
window.removeEventListener('resize', handleResize)
})
}
}3. 定时器管理
javascript
import { onMounted, onUnmounted, ref } from 'vue'
export default {
setup() {
const timer = ref(null)
onMounted(() => {
timer.value = setInterval(() => {
// 定时任务
}, 1000)
})
onUnmounted(() => {
if (timer.value) {
clearInterval(timer.value)
}
})
}
}4. KeepAlive组件的缓存管理
javascript
import { onActivated, onDeactivated, ref } from 'vue'
export default {
setup() {
const isActive = ref(false)
onActivated(() => {
isActive.value = true
// 重新激活时的逻辑
console.log('组件被激活')
})
onDeactivated(() => {
isActive.value = false
// 去激活时的清理逻辑
console.log('组件被缓存')
})
return { isActive }
}
}5.1.12 性能优化考虑
1. 避免在生命周期中进行重复计算
javascript
// 不推荐
onMounted(() => {
// 每次都重新计算
const expensiveValue = heavyComputation()
doSomething(expensiveValue)
})
// 推荐
const expensiveValue = computed(() => heavyComputation())
onMounted(() => {
doSomething(expensiveValue.value)
})2. 合理使用异步操作
javascript
// 推荐:使用异步操作避免阻塞
onMounted(async () => {
const [userData, settingsData] = await Promise.all([
fetchUserData(),
fetchSettings()
])
// 处理数据
})总结
Vue 3的生命周期系统通过精心设计的架构,为开发者提供了强大而灵活的组件管理能力。关键特点包括:
- 统一的钩子注册机制:通过
injectHook函数统一管理所有生命周期钩子 - 错误处理和调试支持:每个钩子都包装了错误处理逻辑
- 异步执行优化:合理使用同步和异步执行,确保性能和正确性
- KeepAlive特殊支持:为缓存组件提供专门的生命周期钩子
- Suspense集成:与异步组件系统无缝集成
理解这些机制不仅有助于正确使用生命周期钩子,更能帮助开发者构建高性能、可维护的Vue应用程序。
