2026.01.28

组件生命周期 -> life cycle

在之前的章节中,我们已经实现了组件的挂载和更新机制。Vue 提供了一套完整的 生命周期系统,允许开发者在组件的不同阶段执行特定的逻辑。生命周期钩子主要分为三个阶段:挂载、更新和卸载。

在之前的章节中,我们已经实现了组件的挂载和更新机制。Vue 提供了一套完整的 生命周期系统,允许开发者在组件的不同阶段执行特定的逻辑。生命周期钩子主要分为三个阶段:挂载、更新和卸载。

生命周期概述

Vue 支持以下生命周期钩子:

1. 挂载阶段

  • onBeforeMount:组件挂载前调用
  • onMounted:组件挂载完成后调用

2. 更新阶段

  • onBeforeUpdate:组件更新前调用
  • onUpdated:组件更新完成后调用

3. 卸载阶段

  • onBeforeUnmount:组件卸载前调用
  • onUnmounted:组件卸载完成后调用

生命周期枚举定义

首先在 packages/runtime-core/src/apiLifecycle.ts 中定义生命周期钩子类型:

export enum LifecycleHooks {
  BEFORE_MOUNT = 'bm',
  MOUNTED = 'm',
  BEFORE_UPDATE = 'bu',
  UPDATED = 'u',
  BEFORE_UNMOUNT = 'bum',
  UNMOUNTED = 'um'
}

设计要点:

  • 使用简写形式的键值存储在组件实例上,减少内存占用
  • 枚举类型确保类型安全,避免硬编码字符串错误

生命周期钩子函数实现

createHook 基础实现

每个生命周期钩子都通过 createHook 工厂函数创建:

const onBeforeMount = createHook(LifecycleHooks.BEFORE_MOUNT)
const onMounted = createHook(LifecycleHooks.MOUNTED)

const onBeforeUpdate = createHook(LifecycleHooks.BEFORE_UPDATE)
const onUpdated = createHook(LifecycleHooks.UPDATED)

const onBeforeUnMount = createHook(LifecycleHooks.BEFORE_UNMOUNT)
const onUnMounted = createHook(LifecycleHooks.UNMOUNTED)

export {
  onBeforeMount,
  onBeforeUnMount,
  onBeforeUpdate,
  onMounted,
  onUnMounted,
  onUpdated
}

createHook 工厂函数

function createHook(type: LifecycleHooks) {
  return (hook, target = getCurrentInstance()) => {
    injectHook(target, hook, type)
  }
}

设计要点:

  • 返回一个闭包函数,可以调用并传入 hook 回调
  • 默认获取当前组件实例,也支持手动指定 target(用于组合式函数)
  • 通过 injectHook 将 hook 注入到对应的生命周期数组中

injectHook 注入机制

function injectHook(target, hook, type) {
  if (target[type] == null) {
    target[type] = []
  }
  const _hook = () => {
    setCurrentInstance(target)
    hook()
    unsetCurrentInstance()
  }
  target[type].push(_hook)
}

逻辑说明:

  1. 数组存储:每个生命周期对应一个数组,支持多个 hook(如 VueUse 等组合式函数场景)
  2. 实例上下文:在 hook 执行时重新设置当前实例,确保 getCurrentInstance 在生命周期内可用
  3. 环境恢复:hook 执行完成后清理当前实例,避免上下文污染

triggerHooks 触发机制

function triggerHooks(instance, type: LifecycleHooks) {
  const hooks = instance[type]
  if (hooks) {
    hooks.forEach(hook => hook())
  }
}

逻辑说明:

  • 获取对应类型的 hook 数组
  • 遍历并执行所有 hook
  • 按注册顺序依次执行

生命周期使用示例

const Comp = {
  setup() {
    onMounted(() => {
      console.log('onMounted')
    })
    return () => {
      return h('div', 'hello')
    }
  }
}

集成到组件更新流程

componentUpdateFn 集成

现在将生命周期钩子集成到 componentUpdateFn 中:

function componentUpdateFn() {
  const { isMounted, vnode, render, subTree, next } = instance

  if (!isMounted) {
    // 挂载前
    triggerHooks(instance, LifecycleHooks.BEFORE_MOUNT)

    const newSubTree = render.call(instance.proxy)
    patch(null, newSubTree, container, anchor)
    vnode.el = newSubTree.el
    instance.subTree = newSubTree
    instance.isMounted = true

    // 挂载后
    triggerHooks(instance, LifecycleHooks.MOUNTED)
  }
  else {
    if (next) {
      updateComponentPreRender(instance, next)
    }
    else {
      next = vnode
    }

    // 更新前
    triggerHooks(instance, LifecycleHooks.BEFORE_UPDATE)

    const prevSubTree = instance.subTree
    const newSubTree = render.call(instance.proxy)

    patch(prevSubTree, newSubTree, container, anchor)
    next.el = newSubTree.el
    instance.subTree = newSubTree

    // 更新后
    triggerHooks(instance, LifecycleHooks.UPDATED)
  }
}

逻辑说明:

  1. 挂载阶段
    • 执行 onBeforeMount hook
    • 渲染并挂载子树
    • 执行 onMounted hook
  2. 更新阶段
    • 更新组件预渲染
    • 执行 onBeforeUpdate hook
    • diff patch 更新子树
    • 执行 onUpdated hook

组件卸载生命周期

unmount 函数扩展

之前的 unmount 函数只处理了 children 的卸载,现在需要添加组件卸载处理:

function unmount(vnode) {
  const { type, shapeFlag } = vnode

  if (shapeFlag & ShapeFlags.COMPONENT) {
    unmountComponent(vnode.component)
  }
  else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
    unmountChildren(vnode.children)
  }

  hostRemove(vnode.el)
}

逻辑说明:

  • 根据不同的 shapeFlag 判断卸载类型
  • 组件类型调用 unmountComponent
  • 数组子节点调用 unmountChildren
  • 最终移除 DOM 元素

unmountComponent 实现

function unmountComponent(instance) {
  // 卸载前
  triggerHooks(instance, LifecycleHooks.BEFORE_UNMOUNT)

  unmount(instance.subTree)

  // 卸载后
  triggerHooks(instance, LifecycleHooks.UNMOUNTED)
}

逻辑说明:

  1. 卸载前:执行 onBeforeUnmount hook
  2. 卸载子树:递归卸载组件的所有子节点
  3. 卸载后:执行 onUnmounted hook

完整测试用例

<div id="app"></div>
<script type="module">
  import {
    h,
    createApp,
    ref,

    onBeforeMount,
    onMounted,
    onBeforeUpdate,
    onUpdated,
    onBeforeUnMount,
    onUnMounted,
  } from '../dist/vue.esm.js'

  const Child = {
    setup(props) {
      onBeforeMount(() => {
        console.log('Child onBeforeMount')
      })
      onMounted(() => {
        console.log('Child onMounted')
      })
      onBeforeUpdate(() => {
        console.log('Child onBeforeUpdate')
      })
      onUpdated(() => {
        console.log('Child onUpdated')
      })
      onBeforeUnMount(() => {
        console.log('Child onBeforeUnmount')
      })
      onUnMounted(() => {
        console.log('Child onUnmounted')
      })

      return () => h('span', props.count)
    }
  }

  const Comp = {
    setup() {
      const p = document.querySelector('#p')
      console.log('setup:', p) // null

      onBeforeMount(() => {
        const p = document.querySelector('#p')
        console.log('onBeforeMount:', p) // null
      })

      onMounted(() => {
        const instance = getCurrentInstance()
        console.log("🚀 ~ instance:", instance)
        const p = document.querySelector('#p')
        console.log('onMounted:', p) // <p>
      })

      onBeforeUpdate(() => {
        console.log('onBeforeUpdate')
      })

      onUpdated(() => {
        console.log('onUpdated')
      })

      onBeforeUnMount(() => {
        console.log('onBeforeUnmount')
      })

      onUnMounted(() => {
        console.log('onUnmounted')
      })

      const count = ref(0)

      return () => h('div', [
        h('p', {
          id: 'p',
          onClick() {
            count.value++
          }
        }, 'parent'),
        h(Child, { count: count.value })
      ])
    }
  }

  const app = createApp(Comp)
  app.mount('#app')

  setTimeout(() => {
    app.unmount()
  }, 2000)
</script>

测试验证:

  1. 挂载流程:验证 onBeforeMountonMounted 的调用顺序
  2. 更新流程:点击父组件触发更新,验证 onBeforeUpdateonUpdated 的调用顺序
  3. 卸载流程:2 秒后卸载应用,验证 onBeforeUnmountonUnmounted 的调用顺序
  4. DOM 访问:验证 onMounted 阶段能够正确访问到 DOM 元素

总结

至此,我们完成了 Vue 组件生命周期系统的完整实现:

1. 生命周期枚举系统

  • 定义了六大生命周期钩子类型
  • 使用简写键值优化存储空间
  • 确保类型安全

2. Hook 注入机制

  • createHook:工厂函数创建生命周期钩子
  • injectHook:将 hook 注入到组件实例的对应数组中
  • 支持多 hook 场景(组合式函数)

3. 实例上下文管理

  • 在 hook 执行时重新设置当前实例
  • 确保 getCurrentInstance 在生命周期内可用
  • 执行完成后清理上下文,避免污染

4. 生命周期触发

  • triggerHooks:统一的生命周期触发函数
  • 按注册顺序依次执行所有 hook
  • 集成到挂载、更新、卸载流程中

5. 完整的卸载流程

  • 扩展 unmount 函数支持组件卸载
  • 实现 unmountComponent 处理组件卸载逻辑
  • 确保子树递归卸载和生命周期正确执行

这套生命周期系统为组件提供了完整的生命周期管理能力,是 Vue 组件化开发的重要基础。通过生命周期钩子,开发者可以在组件的不同阶段执行初始化、清理、监听等逻辑,实现更复杂的业务需求。