The VUE version used in this article is 3.0.5

  • createApp() & mount()
  • setup()
  • render() h()
  • Virtual Dom
  • Lifecycle hooks
  • The Proxy agent
  • reactive() ref()
  • computed()
  • watch()
  • provide() inject()
  • directives()
  • components()

differences

Compared to the 2.x version, the changes to the life cycle are minor.

To register life cycle events using setup(), you need to import the corresponding registration function from the outside. The name of the registration function is prefixed with the on prefix of the old version name, and you need to pay attention to the hump format.

import{onBeforeMount,onMounted, ... }from 'vue'
Copy the code
Options API Hook inside setup
beforeCreate Not needed*
created Not needed*
beforeMount onBeforeMount
mounted onMounted
beforeUpdate onBeforeUpdate
updated onUpdated
beforeUnmount onBeforeUnmount
unmounted onUnmounted
errorCaptured onErrorCaptured
renderTracked onRenderTracked
renderTriggered onRenderTriggered

Because setup is run around the beforeCreate and created lifecycle hooks, you do not need to explicitly define them. In other words, any code that would be written inside those hooks should be written directly in the setup function.

In addition, setup() executes at almost the same time as the original beforeCreate and Created hooks, so you don’t need to explicitly define these two hooks.

The source code to explore

1. Implementation of the lifecycle registration function

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

export type ErrorCapturedHook = (
  err: unknown,
  instance: ComponentPublicInstance | null,
  info: string
) = > boolean | void

export const onErrorCaptured = (
  hook: ErrorCapturedHook,
  target: ComponentInternalInstance | null = currentInstance
) = > {
  injectHook(LifecycleHooks.ERROR_CAPTURED, hook, target)
}
Copy the code

2. Hook injection

export const createHook = <T extends Function = (a)= >any>( lifecycle: LifecycleHooks ) => (hook: T, target: ComponentInternalInstance | null = currentInstance) => // post-create lifecycle registrations are noops during SSR ! isInSSRComponentSetup && injectHook(lifecycle, hook, target)Copy the code

3. Process the function body

export function injectHook(
  type: LifecycleHooks,
  hook: Function& { __weh? :Function },
  target: ComponentInternalInstance | null = currentInstance,
  prepend: boolean = false
) :Function | undefined {
  if (target) {
    // Maintain a stack of functions that need to be executed. In addition to the developer's custom hooks, the framework itself has a lot of code that needs to be executed within each hook.
    const hooks = target[type] || (target[type] = [])
    
    / / encapsulates the hooks
    const wrappedHook =
      hook.__weh ||
      (hook.__weh = (. args: unknown[]) = > {
        if (target.isUnmounted) {
          return
        }
        
        // Suspend collection dependencies to prevent repeated collection
        pauseTracking()
        
        setCurrentInstance(target)
        const res = callWithAsyncErrorHandling(hook, target, type, args)
        setCurrentInstance(null)
        
        // Resume collection
        resetTracking()
        return res
      })
    
    // Whether the header is pushed
    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.`
          : ` `))}}Copy the code

4. Register

// packages/runtime-core/src/componentOptions.ts
export function applyOptions(
  instance: ComponentInternalInstance,
  options: ComponentOptions,
  deferredData: DataFn[] = [],
  deferredWatch: ComponentWatchOptions[] = [],
  deferredProvide: (Data | Function)[] = [],
  asMixin: boolean = false
) {
  
  // Retrieve the hook function from the configuration
  const {
    // composition
    mixins,
    extends: extendsOptions,
    // state
    data: dataOptions,
    computed: computedOptions,
    methods,
    watch: watchOptions,
    provide: provideOptions,
    inject: injectOptions,
    // assets
    components,
    directives,
    // lifecycle
    beforeMount,
    mounted,
    beforeUpdate,
    updated,
    activated,
    deactivated,
    beforeDestroy,
    beforeUnmount,
    destroyed,
    unmounted,
    render,
    renderTracked,
    renderTriggered,
    errorCaptured,
    // public API
    expose
  } = options
  
  / /... Omit irrelevant code
  
  if(! asMixin) { callSyncHook('created',
      LifecycleHooks.CREATED,
      options,
      instance,
      globalMixins
    )
  }
  if (beforeMount) {
    onBeforeMount(beforeMount.bind(publicThis))
  }
  if (mounted) {
    onMounted(mounted.bind(publicThis))
  }
  if (beforeUpdate) {
    onBeforeUpdate(beforeUpdate.bind(publicThis))
  }
  if (updated) {
    onUpdated(updated.bind(publicThis))
  }
  if (activated) {
    onActivated(activated.bind(publicThis))
  }
  if (deactivated) {
    onDeactivated(deactivated.bind(publicThis))
  }
  if (errorCaptured) {
    onErrorCaptured(errorCaptured.bind(publicThis))
  }
  if (renderTracked) {
    onRenderTracked(renderTracked.bind(publicThis))
  }
  if (renderTriggered) {
    onRenderTriggered(renderTriggered.bind(publicThis))
  }
  if (__DEV__ && beforeDestroy) {
    warn(`\`beforeDestroy\` has been renamed to \`beforeUnmount\`.`)}if (beforeUnmount) {
    onBeforeUnmount(beforeUnmount.bind(publicThis))
  }
  if (__DEV__ && destroyed) {
    warn(`\`destroyed\` has been renamed to \`unmounted\`.`)}if (unmounted) {
    onUnmounted(unmounted.bind(publicThis))
  }
}
Copy the code

5. Perform

Finally, the function stack is emptied at the appropriate time during render

// packages/runtime-core/src/renderer.ts
const setupRenderEffect: SetupRenderEffectFn = (instance, initialVNode, container, anchor, parentSuspense, isSVG, optimized) = > {
    // create reactive effect for rendering
    instance.update = effect(function componentEffect() {
      if(! instance.isMounted) {// Mount process
        let vnodeHook: VNodeHook | null | undefined
        const { el, props } = initialVNode
        const { bm, m, parent } = instance

        // beforeMount hook
        if (bm) {
          invokeArrayFns(bm)
        }
				
        / /... Omit irrelevant code

        // mounted hook
        if (m) {
          queuePostRenderEffect(m, parentSuspense)
        }

        // activated hook for keep-alive roots.
        // #1742 activated hook must be accessed after first render
        // since the hook may be injected by a child keep-alive
        const { a } = instance
        if (
          a &&
          initialVNode.shapeFlag & ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE
        ) {
          queuePostRenderEffect(a, parentSuspense)
        }
        instance.isMounted = true

        // #2458: deference mount-only object parameters to prevent memleaks
        initialVNode = container = anchor = null as any
      } else {
				// Update the process
        let { next, bu, u, parent, vnode } = instance
        let originNext = next
        let vnodeHook: VNodeHook | null | undefined
				
        / /... omit

        // beforeUpdate hook
        if (bu) {
          invokeArrayFns(bu)
        }
        
        // updated hook
        if (u) {
          queuePostRenderEffect(u, parentSuspense)
        }
        
      }
    }, __DEV__ ? createDevEffectOptions(instance) : prodEffectOptions)
  }

const unmountComponent = (
    instance: ComponentInternalInstance,
    parentSuspense: SuspenseBoundary | null, doRemove? :boolean
  ) = > {
  
  / /... omit

    const { bum, effects, update, subTree, um } = instance
    // beforeUnmount hook
    if (bum) {
      invokeArrayFns(bum)
    }
  
    // unmounted hook
    if (um) {
      queuePostRenderEffect(um, parentSuspense)
    }
  }
Copy the code

Simple and easy to implement

Based on previous work, simply implement onBeforeMount and onMounted hooks

The online demo