Vue3生命周期浅析

116 阅读4分钟

从入口开始

回忆下vue如何启动

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

很清晰我们先调用createApp函数创建app对象,接着调用mount方法将组件挂载到id位app的container中。

以下分析只考虑在浏览器环境下情况

createApp函数

做什么:
createApp返回App对象

export interface App<HostElement = any> {
  version: string
  config: AppConfig
  use(plugin: Plugin, ...options: any[]): this
  mixin(mixin: ComponentOptions): this
  component(name: string): Component | undefined
  component(name: string, component: Component): this
  directive(name: string): Directive | undefined
  directive(name: string, directive: Directive): this
  mount(
    rootContainer: HostElement | string,
    isHydrate?: boolean,
    isSVG?: boolean
  ): ComponentPublicInstance
  unmount(): void
  provide<T>(key: InjectionKey<T> | string, value: T): this

  // internal, but we need to expose these for the server-renderer and devtools
  _uid: number
  _component: ConcreteComponent
  _props: Data | null
  _container: HostElement | null
  _context: AppContext
  _instance: ComponentInternalInstance | null

  /**
   * v2 compat only
   */
  filter?(name: string): Function | undefined
  filter?(name: string, filter: Function): this

  /**
   * @internal v3 compat only
   */
  _createRoot?(options: ComponentOptions): ComponentPublicInstance
}

mount函数

做什么?
创建vnode并将最后的dom挂载到container中。

如何进行?

function mount(
    rootContainer: HostElement,
    isHydrate?: boolean,
    isSVG?: boolean
) {
    if (!isMounted) {
        const vnode = createVNode(
            rootComponent as ConcreteComponent,
            rootProps
        )
          ... 
          
        if (isHydrate && hydrate) {
            hydrate(vnode as VNode<Node, Element>, rootContainer as any)
        } else {
            render(vnode, rootContainer, isSVG)
        }
        isMounted = true
        app._container = rootContainer
        // for devtools and telemetry
        ;(rootContainer as any).__vue_app__ = app
        ...
    }
}

render函数

函数内部通过createVNode创建vnode,通过render函数创建dom并挂载到container中,可以看到一个非常重要的函数render。 在renderer.ts文件中我们看到render函数的实现.

const render: RootRenderFunction = (vnode, container, isSVG) => {
    if (vnode == null) {
      if (container._vnode) {
        unmount(container._vnode, null, null, true)
      }
    } else {
      patch(container._vnode || null, vnode, container, null, null, null, isSVG)
    }
    flushPostFlushCbs()
    container._vnode = vnode
  }

render函数看上去很简单,如果vnode为空且container已经有vnode则 意味着新树结构需要删除节点,否则调用patch函数更新新旧树,最后则是执行post callback已经更新container的vnode指针。

patch函数

pathc是render函数中一个重要步骤,他包含了创建dom节点,diff比较并且更新dom。
进入patch函数后,由于需要更新的vnnode是component类型
=》进入processComponent函数,当旧节点为空当前且当前节点不为keepalive
=》进入mountComponent此函数创建ComponentInternalInstance
=》接着instance传入setupComponent(instance)

setupComponent函数

setupComponent是对组件初始化的开始

export function setupComponent(
  instance: ComponentInternalInstance,
  isSSR = false
) {
  isInSSRComponentSetup = isSSR

  const { props, children } = instance.vnode
  const isStateful = isStatefulComponent(instance)
  initProps(instance, props, isStateful, isSSR)
  initSlots(instance, children)

  const setupResult = isStateful
    ? setupStatefulComponent(instance, isSSR)
    : undefined
  isInSSRComponentSetup = false
  return setupResult
}

函数逻辑很简单先是初始化props和slots接着执行setupStatefulComponent

setupStatefulComponent函数

setupStatefulComponent会执行setup函数并根据返回值做进一步处理。

if (isFunction(setupResult)) {
    // setup returned an inline render function
    if (__SSR__ && (instance.type as ComponentOptions).__ssrInlineRender) {
      // when the function's name is `ssrRender` (compiled by SFC inline mode),
      // set it as ssrRender instead.
      instance.ssrRender = setupResult
    } else {
      instance.render = setupResult as InternalRenderFunction
    }
  } else if (isObject(setupResult)) { 
    present.instance.setupState = proxyRefs(setupResult)
  } else if (__DEV__ && setupResult !== undefined) {
    ...
  }
  finishComponentSetup(instance, isSSR)

1.当返回值为函数时做被当做render函数
2.当返回值为Object类型时做被当做state值

接着执行finishComponentSetup函数

finishComponentSetup函数

finishComponentSetup会判断当前是否会有render函数如果没有则查看当前是否有template如果有则将其编译成render函数,如果既没有render也没有template则赋给以个空函数。

最后通过applyOptions初始化option中属性。

applyOptions函数

export function applyOptions(instance: ComponentInternalInstance) {
  const options = resolveMergedOptions(instance)
  const publicThis = instance.proxy! as any
  const ctx = instance.ctx

  // do not cache property access on public proxy during state initialization
  shouldCacheAccess = false

  // call beforeCreate first before accessing other options since
  // the hook may mutate resolved options (#2791)
  if (options.beforeCreate) {
    callHook(options.beforeCreate, instance, LifecycleHooks.BEFORE_CREATE)
  }

  const {
    // state
    data: dataOptions,
    computed: computedOptions,
    methods,
    watch: watchOptions,
    provide: provideOptions,
    inject: injectOptions,
    // lifecycle
    created,
    beforeMount,
    mounted,
    beforeUpdate,
    updated,
    activated,
    deactivated,
    beforeDestroy,
    beforeUnmount,
    destroyed,
    unmounted,
    render,
    renderTracked,
    renderTriggered,
    errorCaptured,
    serverPrefetch,
    // public API
    expose,
    inheritAttrs,
    // assets
    components,
    directives,
    filters
  } = options

  const checkDuplicateProperties = __DEV__ ? createDuplicateChecker() : null

  // options initialization order (to be consistent with Vue 2):
  // - props (already done outside of this function)
  // - inject
  // - methods
  // - data (deferred since it relies on `this` access)
  // - computed
  // - watch (deferred since it relies on `this` access)

  if (injectOptions) {
    resolveInjections(
      injectOptions,
      ctx,
      checkDuplicateProperties,
      instance.appContext.config.unwrapInjectedRef
    )
  }

  if (methods) {
    for (const key in methods) {
      const methodHandler = (methods as MethodOptions)[key]
      if (isFunction(methodHandler)) {
        // In dev mode, we use the `createRenderContext` function to define
        // methods to the proxy target, and those are read-only but
        // reconfigurable, so it needs to be redefined here
        if (__DEV__) {
          ...
        } else {
          ctx[key] = methodHandler.bind(publicThis)
        }
        
      } else if (__DEV__) {
        
      }
    }
  }

  if (dataOptions) {
    
    const data = dataOptions.call(publicThis, publicThis)
   
    if (!isObject(data)) {
      
    } else {
      instance.data = reactive(data)
      
    }
  }

  // state initialization complete at this point - start caching access
  shouldCacheAccess = true

  if (computedOptions) {
    ...
  }

  if (watchOptions) {
    ...
  }

  if (provideOptions) {
    ...
  }

  if (created) {
    callHook(created, instance, LifecycleHooks.CREATED)
  }

  function registerLifecycleHook(
    register: Function,
    hook?: Function | Function[]
  ) {
    if (isArray(hook)) {
      hook.forEach(_hook => register(_hook.bind(publicThis)))
    } else if (hook) {
      register((hook as Function).bind(publicThis))
    }
  }

  registerLifecycleHook(onBeforeMount, beforeMount)
  registerLifecycleHook(onMounted, mounted)
  registerLifecycleHook(onBeforeUpdate, beforeUpdate)
  registerLifecycleHook(onUpdated, updated)
  registerLifecycleHook(onActivated, activated)
  registerLifecycleHook(onDeactivated, deactivated)
  registerLifecycleHook(onErrorCaptured, errorCaptured)
  registerLifecycleHook(onRenderTracked, renderTracked)
  registerLifecycleHook(onRenderTriggered, renderTriggered)
  registerLifecycleHook(onBeforeUnmount, beforeUnmount)
  registerLifecycleHook(onUnmounted, unmounted)
  registerLifecycleHook(onServerPrefetch, serverPrefetch)

  if (__COMPAT__) {
    if (
      beforeDestroy &&
      softAssertCompatEnabled(DeprecationTypes.OPTIONS_BEFORE_DESTROY, instance)
    ) {
      registerLifecycleHook(onBeforeUnmount, beforeDestroy)
    }
    if (
      destroyed &&
      softAssertCompatEnabled(DeprecationTypes.OPTIONS_DESTROYED, instance)
    ) {
      registerLifecycleHook(onUnmounted, destroyed)
    }
  }

  if (isArray(expose)) {
    if (expose.length) {
      const exposed = instance.exposed || (instance.exposed = {})
      expose.forEach(key => {
        Object.defineProperty(exposed, key, {
          get: () => publicThis[key],
          set: val => (publicThis[key] = val)
        })
      })
    } else if (!instance.exposed) {
      instance.exposed = {}
    }
  }

  // options that are handled when creating the instance but also need to be
  // applied from mixins
  if (render && instance.render === NOOP) {
    instance.render = render as InternalRenderFunction
  }
  if (inheritAttrs != null) {
    instance.inheritAttrs = inheritAttrs
  }

  // asset options.
  if (components) instance.components = components as any
  if (directives) instance.directives = directives
  if (
    __COMPAT__ &&
    filters &&
    isCompatEnabled(DeprecationTypes.FILTERS, instance)
  ) {
    instance.filters = filters
  }
}

可以清楚的看到声明周期的执行顺序为:

1.合并缓存option调用beforeCreate

2.初始化inject,method,data,computed,watch,provide

3.调用created

4.注册生命周期函数(beforeMount等)设置expose,components,directives,filters

applyOptions函数执行结束后则退出setupComponent函数,接着执行
setupRenderEffect函数

setupRenderEffect函数

1.首先调用beforMount函数
2.接着调用renderComponentRoot方法(内部调用组件的render方法)创建vnode树,并且通过patch方法将vnode树挂载到container上
3.最后调用mounted函数

总结

流程图-vue3生命周期.svg