通过 currentInstance 收集生命周期,并在组件挂载、更新和卸载时触发
2025-11-20 17:41:31
在 Vue 中,可以在 setup 中注册生命周期函数:
import { createApp, h, onBeforeMount, onMounted, ref } from '../dist/vue.esm.js'
const Com = {
setup() {
const count = ref(0)
onBeforeMount(() => {
console.log('before mount')
})
onMounted(() => {
console.log('mounted')
})
return () => {
return h('div', count.value)
}
}
}
createApp(Com).mount('#app')
onMounted(() => {
console.log('mounted')
})
getCurrentInstance
getCurrentInstance: 获取当前正在初始化的组件实例。
这个实例就是 createComponentInstance 创建出来的组件实例。
// packages/runtime-core/src/component.ts
let currentInstance = null
export function setCurrentInstance(instance) {
currentInstance = instance
}
export function getCurrentInstance() {
return currentInstance
}
export function unsetCurrentInstance() {
currentInstance = null
}
在执行组件的 setup 之前,将当前实例保存起来;setup 执行结束后,再把它清空:
// packages/runtime-core/src/component.ts
function setupStatefulComponent(instance) {
const { type } = instance
instance.proxy = new Proxy(instance.ctx, publicInstanceProxyHandlers)
if (isFunction(type.setup)) {
const setupContext = createSetupContext(instance)
setCurrentInstance(instance)
const setupResult = type.setup(instance.props, setupContext)
unsetCurrentInstance()
handleSetupResult(instance, setupResult)
}
// ...
}
这样,在 setup 内部调用 getCurrentInstance() 时,就可以拿到当前组件实例。
生命周期 API 也正是利用这一点,将传入的 hook 保存到当前实例上。
生命周期
创建生命周期 API
先定义生命周期对应的内部字段:
// packages/runtime-core/src/apiLifecycle.ts
export enum LifecycleHooks {
BEFORE_MOUNT = 'bm',
MOUNTED = 'm',
BEFORE_UPDATE = 'bu',
UPDATED = 'u',
BEFORE_UNMOUNT = 'bum',
UNMOUNTED = 'um'
}
这些字段会直接保存到组件实例上:
instance = {
bm: [() => {
}],
m: [() => {
}],
bu: [() => {
}],
u: [() => {
}],
bum: [() => {
}],
um: [() => {
}]
}
通过 createHook 创建具体的生命周期函数:
import { getCurrentInstance } from './component'
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)
function createHook(type) {
return (hook, target = getCurrentInstance()) => {
injectHook(target, hook, type)
}
}
收集生命周期
injectHook 负责把生命周期函数保存到组件实例上。
同一个生命周期可以注册多次,所以实例上保存的是数组:
function injectHook(target, hook, type) {
if (!target) {
return
}
target[type] ??= []
target[type].push(hook)
}
target 默认来自 getCurrentInstance(),所以生命周期函数通常只能在 setup 中调用。
如果在组件初始化之外调用,currentInstance 已经被清空,为 null
触发生命周期
生命周期函数已经保存到了实例上,接下来只需要在合适的时机执行。
export function triggerHooks(instance, type) {
const hooks = instance[type]
if (hooks) {
hooks.forEach(hook => hook())
}
}
然后在组件渲染流程中触发挂载和更新相关的生命周期。
// packages/runtime-core/src/renderer.ts
function setupRenderEffect(instance, container, anchor) {
const componentUpdateFn = () => {
if (!instance.isMounted) {
triggerHooks(instance, LifecycleHooks.BEFORE_MOUNT)
const subTree = instance.render.call(instance.proxy)
patch(null, subTree, container, anchor)
instance.subTree = subTree
instance.isMounted = true
triggerHooks(instance, LifecycleHooks.MOUNTED)
}
else {
triggerHooks(instance, LifecycleHooks.BEFORE_UPDATE)
const prevTree = instance.subTree
const subTree = instance.render.call(instance.proxy)
patch(prevTree, subTree, container, anchor)
instance.subTree = subTree
triggerHooks(instance, LifecycleHooks.UPDATED)
}
}
// ...
}
卸载生命周期
卸载时也需要区分普通元素和组件。
如果卸载的是组件,就先触发组件的卸载生命周期,再卸载组件渲染出来的子树:
function unmount(vnode) {
const { shapeFlag, children } = vnode
if (shapeFlag & ShapeFlags.COMPONENT) {
unmountComponent(vnode.component)
return
}
if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
unmountChildren(children)
}
hostRemove(vnode.el)
}
组件卸载逻辑放到 unmountComponent 中:
function unmountComponent(instance) {
triggerHooks(instance, LifecycleHooks.BEFORE_UNMOUNT)
unmount(instance.subTree)
triggerHooks(instance, LifecycleHooks.UNMOUNTED)
}
小结
- 挂载
- 调用组件
render()之前:onBeforeMount - 完成
patch挂载后:onMounted
- 调用组件
- 更新
- 生成并对比新旧
DOM树前:onBeforeUpdate - 完成
patch后:onUpdated
- 生成并对比新旧
- 卸载 (unmountComponent)
- unmount 前:
onBeforeUnmount - unmount 后:
onUnmounted
- unmount 前: