阿里云主机折上折
  • 微信号
Current Site:Index > The registration mechanism of lifecycle hooks

The registration mechanism of lifecycle hooks

Author:Chuan Chen 阅读数:52346人阅读 分类: Vue.js

Lifecycle Hook Registration Mechanism

Vue3's lifecycle hook registration mechanism is designed based on the Composition API, with core logic concentrated in the packages/runtime-core/src/component.ts file. Unlike Vue2's Options API, Vue3 implements hook registration by calling lifecycle APIs inside the setup() function. This mechanism enables more precise type inference and better logic reuse.

Hook Function Type Definitions

Vue3 internally uses the LifecycleHooks enum to define all supported hook types, with each hook corresponding to an array storing callback functions:

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',
  ERROR_CAPTURED = 'ec',
  RENDER_TRACKED = 'rtc',
  RENDER_TRIGGERED = 'rtg'
}

Each component instance maintains a hooks object, whose properties correspond to the above enum values, with values being arrays of callback functions:

interface ComponentInternalInstance {
  hooks: Record<string, Function[]>
}

Implementation Principle of Registration Process

When calling APIs like onMounted, the actual execution is handled by the injectHook function:

export function injectHook(
  type: LifecycleHooks,
  hook: Function,
  target: ComponentInternalInstance | null = currentInstance
) {
  if (target) {
    const hooks = target[type] || (target[type] = [])
    const wrappedHook = hook.__weh || hook
    hooks.push(wrappedHook)
    return wrappedHook
  }
}

Typical lifecycle API implementations are as follows:

export const onMounted = (hook: Function) => 
  injectHook(LifecycleHooks.MOUNTED, hook)

Asynchronous Dependency Handling Mechanism

Vue3 adds special logic for handling lifecycle hooks in async components. When a Suspense context is detected, some lifecycle hooks are deferred:

export function onMounted(hook: Function) {
  if (getCurrentInstance()) {
    injectHook('m', hook)
  } else if (__FEATURE_SUSPENSE__ && isInSuspense) {
    queueLifecycleHook(hook)
  } else {
    warn(`onMounted is called when there is no active component instance.`)
  }
}

Hook Execution Order Control

The execution order of lifecycle hooks is guaranteed by the internally defined invokeArrayFns function:

export const invokeArrayFns = (fns: Function[], arg?: any) => {
  for (let i = 0; i < fns.length; i++) {
    fns[i](arg)
  }
}

During the component mounting phase, hooks are triggered in the following order:

const setupRenderEffect = () => {
  // Trigger beforeMount
  invokeArrayFns(instance.hooks[LifecycleHooks.BEFORE_MOUNT])
  
  // Execute rendering
  const subTree = renderComponentRoot(instance)
  
  // Trigger mounted
  queuePostRenderEffect(() => {
    invokeArrayFns(instance.hooks[LifecycleHooks.MOUNTED])
  })
}

Special Handling for Server-Side Rendering

For SSR scenarios, some client-specific lifecycles are automatically skipped. This is distinguished internally via the __SSR__ flag:

export function onMounted(hook: Function) {
  if (__SSR__ && !isInSuspense) {
    return
  }
  injectHook(LifecycleHooks.MOUNTED, hook)
}

Error Handling Mechanism

The error capture lifecycle onErrorCaptured has independent processing logic, maintaining a dedicated error handler queue:

export function callWithErrorHandling(
  fn: Function,
  instance: ComponentInternalInstance | null,
  type: ErrorTypes,
  args?: unknown[]
) {
  try {
    return args ? fn(...args) : fn()
  } catch (err) {
    if (instance) {
      const errorInfo = __DEV__ ? ErrorTypeStrings[type] : type
      triggerError(instance, err, errorInfo)
    }
    throw err
  }
}

Interaction with KeepAlive Component

Activation/deactivation lifecycles are deeply integrated with the <KeepAlive> component, triggered within cache control logic:

export function onActivated(hook: Function) {
  registerKeepAliveHook(hook, LifecycleHooks.ACTIVATED)
}

export function onDeactivated(hook: Function) {
  registerKeepAliveHook(hook, LifecycleHooks.DEACTIVATED)
}

Additional Checks in Development Environment

In development mode, extra validations are added for lifecycle usage:

export function validateLifecycleHook(
  hook: Function,
  instance: ComponentInternalInstance
) {
  if (__DEV__ && hook && hook.length > 0) {
    warn(
      `Lifecycle hook "${hook.name}" has ${hook.length} parameters, ` +
      `but the callback only accepts a single argument.`
    )
  }
}

Performance Optimization Measures

Vue3 optimizes high-frequency update lifecycles (e.g., beforeUpdate):

export function queueLifecycleHook(hook: Function) {
  if (__DEV__ && !currentInstance) {
    warn(`Lifecycle hook "${hook.name}" is called outside of setup().`)
    return
  }
  if (isHmrUpdating) {
    return
  }
  nextTickPreFlushCbs.push(hook)
}

Integration with Scheduling System

The execution timing of lifecycle hooks is tightly coupled with Vue's scheduling system:

const flushPostFlushCbs = () => {
  for (let i = 0; i < postFlushCbs.length; i++) {
    postFlushCbs[i]()
  }
}

Type System Support

TypeScript type definitions ensure type safety for lifecycle hooks:

declare type LifecycleHook<T extends Function = Function> = T & {
  __weh?: T
}

export declare interface ComponentCustomOptions {
  beforeCreate?(this: ComponentPublicInstance): void
  created?(this: ComponentPublicInstance): void
  // ...other hook types
}

Compatibility Handling Between Hooks and Options API

For cases using both Options API and Composition API, Vue3 merges the processing:

export function applyOptions(instance: ComponentInternalInstance) {
  const options = instance.type
  // Process beforeCreate
  if (options.beforeCreate) {
    callHook(options.beforeCreate, instance, LifecycleHooks.BEFORE_CREATE)
  }
  
  // Process hooks registered via Composition API
  if (instance.hooks) {
    Object.keys(instance.hooks).forEach(hook => {
      registerLifecycleHook(instance, hook as LifecycleHooks)
    })
  }
}

Interaction Between Lifecycles and Reactive System

Some lifecycles trigger specific operations in the reactive system:

export function triggerBeforeUpdate() {
  if (instance.hooks[LifecycleHooks.BEFORE_UPDATE]) {
    pauseTracking()
    invokeArrayFns(instance.hooks[LifecycleHooks.BEFORE_UPDATE])
    resetTracking()
  }
}

Custom Renderer Support

Custom renderers can extend or modify lifecycle behavior:

export function createRenderer(options: RendererOptions) {
  const {
    patchProp,
    createElement,
    // ...other methods
  } = options

  function mountComponent() {
    // Custom mounting logic
    callHook(instance, LifecycleHooks.BEFORE_MOUNT)
    // ...
  }
}

Coordination with Teleport Component

The Teleport component specially handles certain lifecycles:

export function resolveTarget(props: TeleportProps) {
  if (isMounted) {
    callHook(instance, LifecycleHooks.BEFORE_UPDATE)
  }
  // ...resolve target location
}

Lifecycles and VNode Association

Some lifecycles access VNode-related information:

export function renderComponentRoot(instance: ComponentInternalInstance): VNode {
  callHook(instance, LifecycleHooks.BEFORE_RENDER)
  const result = instance.render.call(proxy)
  callHook(instance, LifecycleHooks.RENDERED)
  return result
}

DevTools Integration

DevTools listens to lifecycle events for debugging:

export function emitLifecycleHook(
  instance: ComponentInternalInstance,
  hook: LifecycleHooks
) {
  if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
    devtools.emitLifecycle(instance, hook)
  }
}

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

Front End Chuan

Front End Chuan, Chen Chuan's Code Teahouse 🍵, specializing in exorcising all kinds of stubborn bugs 💻. Daily serving baldness-warning-level development insights 🛠️, with a bonus of one-liners that'll make you laugh for ten years 🐟. Occasionally drops pixel-perfect romance brewed in a coffee cup ☕.