通过 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