Skip to content

第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
  }
}

关键执行顺序:

  1. beforeMount - 同步执行,在DOM渲染之前
  2. 组件渲染和DOM挂载
  3. 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函数或生命周期钩子中调用,这是因为:

  1. 只有在这些阶段,currentInstance才会被正确设置
  2. 确保了组件实例的正确性和安全性
  3. 避免了在错误的上下文中访问组件实例

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的生命周期系统通过精心设计的架构,为开发者提供了强大而灵活的组件管理能力。关键特点包括:

  1. 统一的钩子注册机制:通过injectHook函数统一管理所有生命周期钩子
  2. 错误处理和调试支持:每个钩子都包装了错误处理逻辑
  3. 异步执行优化:合理使用同步和异步执行,确保性能和正确性
  4. KeepAlive特殊支持:为缓存组件提供专门的生命周期钩子
  5. Suspense集成:与异步组件系统无缝集成

理解这些机制不仅有助于正确使用生命周期钩子,更能帮助开发者构建高性能、可维护的Vue应用程序。


微信公众号二维码