Skip to content

生命周期

【官方解释】每个 Vue 组件实例在创建时都需要经历一系列的初始化步骤,比如设置好数据侦听,编译模板,挂载实例到 DOM,以及在数据改变时更新 DOM。在此过程中,它也会运行被称为生命周期钩子的函数,让开发者有机会在特定阶段运行自己的代码。


下表是对Vue 3组合式API的生命周期钩子的理解和用法的简要说明:

钩子函数说明用法示例
onBeforeMount在组件挂载之前被调用,此时模板编译已完成,但尚未将组件插入到DOM中 onBeforeMount(() => { // 在组件挂载之前的逻辑 })
onMounted在组件挂载到DOM后被调用 onMounted(() => { // 在组件挂载到DOM后的逻辑 })
onBeforeUnmount在组件卸载之前被调用,此时组件仍然在DOM中 onBeforeUnmount(() => { // 在组件卸载之前的逻辑 })
onUnmounted在组件卸载之后被调用,此时组件已从DOM中移除 onUnmounted(() => { // 在组件卸载后的逻辑 })
onBeforeUpdate在组件更新之前被调用,可用于在更新之前访问旧的DOM状态或数据 onBeforeUpdate(() => { // 在组件更新之前的逻辑 })
onUpdated在组件更新之后被调用,此时DOM已经更新 onUpdated(() => { // 在组件更新后的逻辑 })
onRenderTracked在组件渲染时被调用,用于追踪渲染过程中的依赖关系 onRenderTracked((event) => { // 在组件渲染时的逻辑 })
onRenderTriggered在组件渲染时被调用,用于追踪触发渲染的依赖关系 onRenderTriggered((event) => { // 在组件渲染时的逻辑 })

选项式的生命周期钩子图:

Vue官方生命周期图例

测试代码

vue
<template>
    <div>
        <!-- 使用v-if组件会直接销毁,而v-show则是隐藏 -->
        <life v-if="station"/>
        <!-- 用来销毁life组件,使onBeforeUnmount和onUnmounted被调用 -->
        <Button @click="station=!station" hoverF="销毁" hoverS="click!" />
    </div>
</template>

<script setup lang='ts'>
import { ref } from 'vue';
import Button from '../../组件库/Button.vue'
import life from './life.vue'

const station=ref<boolean>(true)
</script>
<style scoped></style>
vue
<template>
    <div>
        <div ref="div" style="font-size: 20px;font-weight: 600;color: #ecc208;">{{ str }}</div>
        <Button @click="change" hoverF="修改" hoverS="click!" />
    </div>
</template>

<script setup lang='ts'>
import { ref, getCurrentInstance, onBeforeMount, onBeforeUnmount, onBeforeUpdate, onMounted, onUnmounted, onUpdated, onRenderTracked, onRenderTriggered } from 'vue';
import Button from '../../组件库/Button.vue'

// import { ref } from 'vue'
// 在使用options API时是没有beforeCreate和created这两个生命周期的
// setup语法糖模式是没有beforeCreate和created这两个生命周期的
console.log('setup')

const str = ref<string>('Rarrot')
const div = ref<HTMLDivElement>()
const change = () => {
    str.value = "我被修改了"
}
// 通过getCurrentInstance查看当前组件的实例,也是一个hook
const instance=getCurrentInstance()
console.log("🚀  instance", instance)

// 组件第一次渲染前调用,此时根元素不存在,无法访问到DOM
onBeforeMount(() => {
    console.log("组件第一次渲染前调用", div.value)//undefined
})

// 组件第一次渲染后调用,该元素现在可用,允许直接DOM访问
onMounted(() => {
    console.log("组件第一次渲染后调用", div.value)//<div>Rarrot</div>
})

// 获取更新之前的dom
onBeforeUpdate(() => {
    console.log("更新前")
})

// 获取更新之后的dom
onUpdated(() => {
    console.log("更新后")
})

// 销毁组件之前调用
onBeforeUnmount(() => {
    console.log("销毁组件之前调用")
})

// 销毁组件之后调用
onUnmounted(() => {
    console.log("销毁组件之后调用")
})

// 以下两个钩子主要用于调试,onRenderTracked用来获取收集的依赖
onRenderTracked((e) => {
    // 打印effect等对象
    console.log(e)
})

// 依赖被修改时则会调用此钩子
onRenderTriggered((e) => {
    console.log(e)
})
</script>
<style scoped></style>

测试用例

可以用F12打开 开发者工具 查看

Rarrot

源码解读

通过使用一个枚举类型将钩子挂载到组件实例上:

typescript
// 生命周期的钩子函数时在生命周期的各个阶段去执行,所以把这些钩子挂载到组件的实例上面
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 onServerPrefetch = createHook(LifecycleHooks.SERVER_PREFETCH)

export type DebuggerHook = (e: DebuggerEvent) => void
export const onRenderTriggered = createHook<DebuggerHook>(
  LifecycleHooks.RENDER_TRIGGERED
)
export const onRenderTracked = createHook<DebuggerHook>(
  LifecycleHooks.RENDER_TRACKED
)
typescript
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'
}

可以通过getCurrentInstance来查看该组件注册的实例


由于钩子函数传入的参数只有第一个参数(lifecycle)是不同的,所以使用createHook实现一个科里化将其封装起来,最后调用injectHook(),源码如下:

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)
    // 创建后生命周期注册在 SSR 期间是无操作的(serverPrefetch 除外)
    (!isInSSRComponentSetup || lifecycle === LifecycleHooks.SERVER_PREFETCH) &&
    injectHook(lifecycle, (...args: unknown[]) => hook(...args), target)

用于注册钩子的函数injectHook()

展开代码
typescript
export function injectHook(
  type: LifecycleHooks,
  hook: Function & { __weh?: Function },
  target: ComponentInternalInstance | null = currentInstance,
  prepend: boolean = false
): Function | undefined {
  if (target) {
    // 如果有hook函数直接返回,否则创建一个空数组,hooks为Function类型
    const hooks = target[type] || (target[type] = [])
    // 缓存injected hooks的错误处理包装器,因此相同的hook可以由调度程序正确删除重复数据。
    //  “__weh”代表“有错误要处理”。
    const wrappedHook =
      hook.__weh ||
      (hook.__weh = (...args: unknown[]) => {
        if (target.isUnmounted) {
          return
        }
        // disable tracking inside all lifecycle hooks
        // since they can potentially be called inside effects.
        // 停止依赖收集,避免重复收集依赖
        pauseTracking()
        // Set currentInstance during hook invocation.
        // This assumes the hook does not synchronously trigger other hooks, which
        // can only be false when the user does something really funky.
        // 设置target为当前实例
        setCurrentInstance(target)
        const res = callWithAsyncErrorHandling(hook, target, type, args)
        unsetCurrentInstance()//清空当前组件实例
        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().` +
        (__FEATURE_SUSPENSE__
          ? ` If you are using async setup(), make sure to register lifecycle ` +
            `hooks before the first await statement.`
          : ``)
    )
  }
}

具体对于dom的操作,每个阶段的调用的代码在源码的./runtime-core/renderer.ts中。

Released under the MIT License.