Vue 组件化
Vue 组件化
createComponent : 创建组件 VNode
在 Vue.prototype._render 进行 Virtual DOM 渲染时,执行 vnode = render.call(vm._renderProxy, vm.$createElement),通过 vm.$createElement 最终调用 createElement (最终调用 _createElement) 创建 Virtual DOM 。
_createElement(context, tag, data, children, normalizationType) 方法中,会对参数 tag 进行判断,如果是一个普通的 html 标签,则会实例化一个普通的 VNode 节点,否则,会通过 createComponent 方法创建一个组件 VNode 。
【render】过程:调用的 _createElement 方法
// src\core\vdom\create-element.ts
export function _createElement(
  context: Component,
  tag?: string | Component | Function | Object,
  data?: VNodeData,
  children?: any,
  normalizationType?: number
): VNode | Array<VNode> {
  if (isDef(data) && isDef((data as any).__ob__)) {
    __DEV__ &&
      warn(
        `Avoid using observed data object as vnode data: ${JSON.stringify(
          data
        )}\n` + 'Always create fresh vnode data objects in each render!',
        context
      )
    return createEmptyVNode()
  }
  // object syntax in v-bind
  if (isDef(data) && isDef(data.is)) {
    tag = data.is
  }
  if (!tag) {
    // in case of component :is set to falsy value
    return createEmptyVNode()
  }
  // warn against non-primitive key
  if (__DEV__ && isDef(data) && isDef(data.key) && !isPrimitive(data.key)) {
    warn(
      'Avoid using non-primitive value as key, ' +
        'use string/number value instead.',
      context
    )
  }
  // support single function children as default scoped slot
  if (isArray(children) && isFunction(children[0])) {
    data = data || {}
    data.scopedSlots = { default: children[0] }
    children.length = 0
  }
  if (normalizationType === ALWAYS_NORMALIZE) {
    children = normalizeChildren(children)
  } else if (normalizationType === SIMPLE_NORMALIZE) {
    children = simpleNormalizeChildren(children)
  }
  let vnode, ns
  if (typeof tag === 'string') {
    let Ctor
    ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag)
    if (config.isReservedTag(tag)) {
      // platform built-in elements
      if (
        __DEV__ &&
        isDef(data) &&
        isDef(data.nativeOn) &&
        data.tag !== 'component'
      ) {
        warn(
          `The .native modifier for v-on is only valid on components but it was used on <${tag}>.`,
          context
        )
      }
      vnode = new VNode(
        config.parsePlatformTagName(tag),
        data,
        children,
        undefined,
        undefined,
        context
      )
    } else if (
      (!data || !data.pre) &&
      isDef((Ctor = resolveAsset(context.$options, 'components', tag)))
    ) {
      // component
      vnode = createComponent(Ctor, data, context, children, tag)
    } else {
      // unknown or unlisted namespaced elements
      // check at runtime because it may get assigned a namespace when its
      // parent normalizes children
      vnode = new VNode(tag, data, children, undefined, undefined, context)
    }
  } else {
    // direct component options / constructor
    vnode = createComponent(tag as any, data, context, children)
  }
  if (isArray(vnode)) {
    return vnode
  } else if (isDef(vnode)) {
    if (isDef(ns)) applyNS(vnode, ns)
    if (isDef(data)) registerDeepBindings(data)
    return vnode
  } else {
    return createEmptyVNode()
  }
}【render】过程:通过的 createComponent 方法创建 VNode
// src\core\vdom\create-component.ts
export function createComponent(
  Ctor: typeof Component | Function | ComponentOptions | void,
  data: VNodeData | undefined,
  context: Component,
  children?: Array<VNode>,
  tag?: string
): VNode | Array<VNode> | void {
  if (isUndef(Ctor)) {
    return
  }
  const baseCtor = context.$options._base
  // plain options object: turn it into a constructor
  if (isObject(Ctor)) {
    Ctor = baseCtor.extend(Ctor as typeof Component)
  }
  // if at this stage it's not a constructor or an async component factory,
  // reject.
  if (typeof Ctor !== 'function') {
    if (__DEV__) {
      warn(`Invalid Component definition: ${String(Ctor)}`, context)
    }
    return
  }
  // async component
  let asyncFactory
  // @ts-expect-error
  if (isUndef(Ctor.cid)) {
    asyncFactory = Ctor
    Ctor = resolveAsyncComponent(asyncFactory, baseCtor)
    if (Ctor === undefined) {
      // return a placeholder node for async component, which is rendered
      // as a comment node but preserves all the raw information for the node.
      // the information will be used for async server-rendering and hydration.
      return createAsyncPlaceholder(asyncFactory, data, context, children, tag)
    }
  }
  data = data || {}
  // resolve constructor options in case global mixins are applied after
  // component constructor creation
  resolveConstructorOptions(Ctor as typeof Component)
  // transform component v-model data into props & events
  if (isDef(data.model)) {
    // @ts-expect-error
    transformModel(Ctor.options, data)
  }
  // extract props
  // @ts-expect-error
  const propsData = extractPropsFromVNodeData(data, Ctor, tag)
  // functional component
  // @ts-expect-error
  if (isTrue(Ctor.options.functional)) {
    return createFunctionalComponent(
      Ctor as typeof Component,
      propsData,
      data,
      context,
      children
    )
  }
  // extract listeners, since these needs to be treated as
  // child component listeners instead of DOM listeners
  const listeners = data.on
  // replace with listeners with .native modifier
  // so it gets processed during parent component patch.
  data.on = data.nativeOn
  // @ts-expect-error
  if (isTrue(Ctor.options.abstract)) {
    // abstract components do not keep anything
    // other than props & listeners & slot
    // work around flow
    const slot = data.slot
    data = {}
    if (slot) {
      data.slot = slot
    }
  }
  // install component management hooks onto the placeholder node
  installComponentHooks(data)
  // return a placeholder vnode
  // @ts-expect-error
  const name = getComponentName(Ctor.options) || tag
  const vnode = new VNode(
    // @ts-expect-error
    `vue-component-${Ctor.cid}${name ? `-${name}` : ''}`,
    data,
    undefined,
    undefined,
    undefined,
    context,
    // @ts-expect-error
    { Ctor, propsData, listeners, tag, children },
    asyncFactory
  )
  return vnode
}构造子类构造函数
在开发过程中,编写 Vue 组件通常会创建一个普通对象, export 是一个对象。示例如下:
// App.vue
import HelloWorld from './components/HelloWorld'
export default {
  name: 'app',
  components: {
    HelloWorld
  }
}在 createComponent 方法中,执行 const baseCtor = context.$options._base 时,其中:
baseCtor: Vue 构造函数。通过context.$options._base获取 Vue 构造函数,在执行initGlobalAPI方法时,通过Vue.options._base = Vue定义。context.$options:Vue 实例的初始化选项。在 Vue 构造函数中,执行this._init方法,进而调用Vue.prototype._init,执行mergeOptions进行options合并和挂载。
// src\core\vdom\create-component.ts
// createComponent 方法
const baseCtor = context.$options._base
// plain options object: turn it into a constructor
if (isObject(Ctor)) {
  Ctor = baseCtor.extend(Ctor as typeof Component)
}在 createComponent 方法中,执行 Ctor = baseCtor.extend(Ctor as typeof Component) 时, baseCtor 指向 Vue, 则 baseCtor.extend 实际执行 Vue.extend 。
Vue.extend 的作用就是构造一个 Vue 的子类,使用原型继承的方式把一个纯对象转换一个继承于 Vue 的构造器 Sub 并返回。实例化 Sub 时,会执行 this.init 进行 Vue 实例初始化逻辑。同时,会 Sub 子类对扩展、初始化等操作:
- 对 
Sub这个对象本身扩展了一些属性:扩展 options 、 添加全局 API 等。 - 对配置中的 
props和computed进行初始化工作 - 对于 
Sub构造函数进行缓存,避免多次执行Vue.extend的时候对同一个子组件重复构造 
Vue.extend
// src\core\global-api\extend.ts
export function initExtend(Vue: GlobalAPI) {
  /**
   * Each instance constructor, including Vue, has a unique
   * cid. This enables us to create wrapped "child
   * constructors" for prototypal inheritance and cache them.
   */
  Vue.cid = 0
  let cid = 1
  /**
   * Class inheritance
   */
  Vue.extend = function (extendOptions: any): typeof Component {
    extendOptions = extendOptions || {}
    const Super = this
    const SuperId = Super.cid
    const cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {})
    if (cachedCtors[SuperId]) {
      return cachedCtors[SuperId]
    }
    const name =
      getComponentName(extendOptions) || getComponentName(Super.options)
    if (__DEV__ && name) {
      validateComponentName(name)
    }
    const Sub = function VueComponent(this: any, options: any) {
      this._init(options)
    } as unknown as typeof Component
    Sub.prototype = Object.create(Super.prototype)
    Sub.prototype.constructor = Sub
    Sub.cid = cid++
    Sub.options = mergeOptions(Super.options, extendOptions)
    Sub['super'] = Super
    // For props and computed properties, we define the proxy getters on
    // the Vue instances at extension time, on the extended prototype. This
    // avoids Object.defineProperty calls for each instance created.
    if (Sub.options.props) {
      initProps(Sub)
    }
    if (Sub.options.computed) {
      initComputed(Sub)
    }
    // allow further extension/mixin/plugin usage
    Sub.extend = Super.extend
    Sub.mixin = Super.mixin
    Sub.use = Super.use
    // create asset registers, so extended classes
    // can have their private assets too.
    ASSET_TYPES.forEach(function (type) {
      Sub[type] = Super[type]
    })
    // enable recursive self-lookup
    if (name) {
      Sub.options.components[name] = Sub
    }
    // keep a reference to the super options at extension time.
    // later at instantiation we can check if Super's options have
    // been updated.
    Sub.superOptions = Super.options
    Sub.extendOptions = extendOptions
    Sub.sealedOptions = extend({}, Sub.options)
    // cache constructor
    cachedCtors[SuperId] = Sub
    return Sub
  }
}
function initProps(Comp: typeof Component) {
  const props = Comp.options.props
  for (const key in props) {
    proxy(Comp.prototype, `_props`, key)
  }
}
function initComputed(Comp: typeof Component) {
  const computed = Comp.options.computed
  for (const key in computed) {
    defineComputed(Comp.prototype, key, computed[key])
  }
}安装组件钩子函数
在 createComponent 方法中,执行 installComponentHooks 安装组件钩子函数。
installComponentHooks 的过程就是将 componentVNodeHooks 的钩子函数合并到 data.hook 中,在 VNode 执行 patch 的过程中执行相关的钩子函数。在合并过程中,如果某个时机的钩子已存在 data.hook 中,那么通过执行 mergeHook 函数合并,在最终执行的时候,依次执行这两个钩子函数。
installComponentHooks
// src\core\vdom\create-component.ts
// inline hooks to be invoked on component VNodes during patch
const componentVNodeHooks = {
  init(vnode: VNodeWithData, hydrating: boolean): boolean | void {
    if (
      vnode.componentInstance &&
      !vnode.componentInstance._isDestroyed &&
      vnode.data.keepAlive
    ) {
      // kept-alive components, treat as a patch
      const mountedNode: any = vnode // work around flow
      componentVNodeHooks.prepatch(mountedNode, mountedNode)
    } else {
      const child = (vnode.componentInstance = createComponentInstanceForVnode(
        vnode,
        activeInstance
      ))
      child.$mount(hydrating ? vnode.elm : undefined, hydrating)
    }
  },
  prepatch(oldVnode: MountedComponentVNode, vnode: MountedComponentVNode) {
    const options = vnode.componentOptions
    const child = (vnode.componentInstance = oldVnode.componentInstance)
    updateChildComponent(
      child,
      options.propsData, // updated props
      options.listeners, // updated listeners
      vnode, // new parent vnode
      options.children // new children
    )
  },
  insert(vnode: MountedComponentVNode) {
    const { context, componentInstance } = vnode
    if (!componentInstance._isMounted) {
      componentInstance._isMounted = true
      callHook(componentInstance, 'mounted')
    }
    if (vnode.data.keepAlive) {
      if (context._isMounted) {
        // vue-router#1212
        // During updates, a kept-alive component's child components may
        // change, so directly walking the tree here may call activated hooks
        // on incorrect children. Instead we push them into a queue which will
        // be processed after the whole patch process ended.
        queueActivatedComponent(componentInstance)
      } else {
        activateChildComponent(componentInstance, true /* direct */)
      }
    }
  },
  destroy(vnode: MountedComponentVNode) {
    const { componentInstance } = vnode
    if (!componentInstance._isDestroyed) {
      if (!vnode.data.keepAlive) {
        componentInstance.$destroy()
      } else {
        deactivateChildComponent(componentInstance, true /* direct */)
      }
    }
  }
}
const hooksToMerge = Object.keys(componentVNodeHooks)
function installComponentHooks(data: VNodeData) {
  const hooks = data.hook || (data.hook = {})
  for (let i = 0; i < hooksToMerge.length; i++) {
    const key = hooksToMerge[i]
    const existing = hooks[key]
    const toMerge = componentVNodeHooks[key]
    // @ts-expect-error
    if (existing !== toMerge && !(existing && existing._merged)) {
      hooks[key] = existing ? mergeHook(toMerge, existing) : toMerge
    }
  }
}
function mergeHook(f1: any, f2: any): Function {
  const merged = (a, b) => {
    // flow complains about extra args which is why we use any
    f1(a, b)
    f2(a, b)
  }
  merged._merged = true
  return merged
}实例化 VNode
在 createComponent 中,安装组件钩子函数后,会通过 new VNode 实例化一个 vnode 返回。与普通元素节点的 vnode 不同,组件的 vnode 是没有 children 的。
// return a placeholder vnode
// @ts-expect-error
const name = getComponentName(Ctor.options) || tag
const vnode = new VNode(
  // @ts-expect-error
  `vue-component-${Ctor.cid}${name ? `-${name}` : ''}`,
  data,
  undefined,
  undefined,
  undefined,
  context,
  // @ts-expect-error
  { Ctor, propsData, listeners, tag, children },
  asyncFactory
)
return vnodepatch : 将组件 VNode 渲染真实 DOM
在 _render 过程中,通过 vm.$createElement 调用 createElement ,使用 createComponent 创建了组件 VNode 后,会调用 vm._update 执行 vm.__patch__ 将 VNode 转换成真实 DOM 节点。
在 patch 过程中,会调用 createElm 创建元素节点,其中,会判断 patch 的辅助函数 createComponent(vnode, insertedVnodeQueue, parentElm, refElm) 的返回值。
在 patch 的辅助函数 createComponent 函数中,对 vnode.data 进行了判断。如果 vnode 是一个组件 VNode,条件满足,并得到 i 为 init 钩子函数,对组件进行初始化。
【patch】 过程: 调用的 patch 辅助函数 createComponent
function createComponent(vnode, insertedVnodeQueue, parentElm, refElm) {
  let i = vnode.data
  if (isDef(i)) {
    const isReactivated = isDef(vnode.componentInstance) && i.keepAlive
    if (isDef((i = i.hook)) && isDef((i = i.init))) {
      i(vnode, false /* hydrating */)
    }
    // after calling the init hook, if the vnode is a child component
    // it should've created a child instance and mounted it. the child
    // component also has set the placeholder vnode's elm.
    // in that case we can just return the element and be done.
    if (isDef(vnode.componentInstance)) {
      initComponent(vnode, insertedVnodeQueue)
      insert(parentElm, vnode.elm, refElm)
      if (isTrue(isReactivated)) {
        reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm)
      }
      return true
    }
  }
}在执行 _render 过程中,通过 _createElement,调用 createComponent 方法,执行 installComponentHooks 安装组件钩子函数,包含 init 钩子函数。在不考虑 keep-alive 的情况下,通过 createComponentInstanceForVnode 创建一个 Vue 的实例,然后调用 $mount 方法挂载子组件。
createComponentInstanceForVnode 函数构造一个内部组件的参数,然后执行 new vnode.componentOptions.Ctor(options) 进行子组件实例化,并执行实例的 this._init 方法。
vnode.componentOptions.Ctor对应的是子组件的构造函数,实际上是继承于 Vue 的一个构造器Sub,相当于new Sub(options)options参数options._isComponent: 为true表示为一个组件options.parent: 表示当前激活的组件实例
【_render】 过程:调用 installComponentHooks 安装组件的 init 钩子函数
// src\core\vdom\create-component.ts
// inline hooks to be invoked on component VNodes during patch
const componentVNodeHooks = {
  init(vnode: VNodeWithData, hydrating: boolean): boolean | void {
    if (
      vnode.componentInstance &&
      !vnode.componentInstance._isDestroyed &&
      vnode.data.keepAlive
    ) {
      // kept-alive components, treat as a patch
      const mountedNode: any = vnode // work around flow
      componentVNodeHooks.prepatch(mountedNode, mountedNode)
    } else {
      const child = (vnode.componentInstance = createComponentInstanceForVnode(
        vnode,
        activeInstance
      ))
      child.$mount(hydrating ? vnode.elm : undefined, hydrating)
    }
  },
  // ... 省略其他钩子函数定义 : prepatch、insert、destroy
}
export function createComponentInstanceForVnode(
  // we know it's MountedComponentVNode but flow doesn't
  vnode: any,
  // activeInstance in lifecycle state
  parent?: any
): Component {
  const options: InternalComponentOptions = {
    _isComponent: true,
    _parentVnode: vnode,
    parent
  }
  // check inline-template render functions
  const inlineTemplate = vnode.data.inlineTemplate
  if (isDef(inlineTemplate)) {
    options.render = inlineTemplate.render
    options.staticRenderFns = inlineTemplate.staticRenderFns
  }
  return new vnode.componentOptions.Ctor(options)
}子组件实例化的过程中,调用 this._init 执行相关逻辑会有些不同:
合并
options: 在Vue.prototype._init方法中,判断options._isComponent为true会执行initInternalComponent(vm, options)。其中,opts.parent = options.parentopts._parentVnode = parentVnode
是通过
createComponentInstanceForVnode(vnode, parent)函数传入的参数,合并到内部的选项$options中。子组件接管
$mount: 子组件初始化的时候不传el参数,则,回到组件init钩子函数,完成实例化的_init后,执行child.$mount(hydrating ? vnode.elm : undefined, hydrating)。hydrating: 为true一般表示服务端渲染的情况child.$mount(hydrating ? vnode.elm : undefined, hydrating)对于客户端渲染,相当于child.$mount(undefined, false),最终会调用mountComponent方法,进而执行vm._render()方法
【子组件实例化过程】:调用的 Vue.prototype._init 进行初始化
// src\core\instance\init.ts
export function initMixin(Vue: typeof Component) {
  Vue.prototype._init = function (options?: Record<string, any>) {
    const vm: Component = this
    // a uid
    vm._uid = uid++
    let startTag, endTag
    /* istanbul ignore if */
    if (__DEV__ && config.performance && mark) {
      startTag = `vue-perf-start:${vm._uid}`
      endTag = `vue-perf-end:${vm._uid}`
      mark(startTag)
    }
    // a flag to mark this as a Vue instance without having to do instanceof
    // check
    vm._isVue = true
    // avoid instances from being observed
    vm.__v_skip = true
    // effect scope
    vm._scope = new EffectScope(true /* detached */)
    vm._scope._vm = true
    // merge options
    if (options && options._isComponent) {
      // optimize internal component instantiation
      // since dynamic options merging is pretty slow, and none of the
      // internal component options needs special treatment.
      initInternalComponent(vm, options as any)
    } else {
      vm.$options = mergeOptions(
        resolveConstructorOptions(vm.constructor as any),
        options || {},
        vm
      )
    }
    /* istanbul ignore else */
    if (__DEV__) {
      initProxy(vm)
    } else {
      vm._renderProxy = vm
    }
    // expose real self
    vm._self = vm
    initLifecycle(vm)
    initEvents(vm)
    initRender(vm)
    callHook(vm, 'beforeCreate', undefined, false /* setContext */)
    initInjections(vm) // resolve injections before data/props
    initState(vm)
    initProvide(vm) // resolve provide after data/props
    callHook(vm, 'created')
    /* istanbul ignore if */
    if (__DEV__ && config.performance && mark) {
      vm._name = formatComponentName(vm, false)
      mark(endTag)
      measure(`vue ${vm._name} init`, startTag, endTag)
    }
    if (vm.$options.el) {
      vm.$mount(vm.$options.el)
    }
  }
}子组件进行 _render 渲染 Virtual DOM 过程的时候,其中
_parentVnode: 当前组件的父 VNodevm.$vnode: 当前 vnode 的 parentvnode: 通过render函数生成的当前组件的渲染 vnode
【_render】 过程:调用 Vue.prototype._render 渲染 Virtual DOM
// src\core\instance\render.ts
Vue.prototype._render = function (): VNode {
  const vm: Component = this
  const { render, _parentVnode } = vm.$options
  if (_parentVnode && vm._isMounted) {
    vm.$scopedSlots = normalizeScopedSlots(
      vm.$parent!,
      _parentVnode.data!.scopedSlots,
      vm.$slots,
      vm.$scopedSlots
    )
    if (vm._slotsProxy) {
      syncSetupSlots(vm._slotsProxy, vm.$scopedSlots)
    }
  }
  // set parent vnode. this allows render functions to have access
  // to the data on the placeholder node.
  vm.$vnode = _parentVnode!
  // render self
  let vnode
  try {
    // There's no need to maintain a stack because all render fns are called
    // separately from one another. Nested component's render fns are called
    // when parent component is patched.
    setCurrentInstance(vm)
    currentRenderingInstance = vm
    vnode = render.call(vm._renderProxy, vm.$createElement)
  } catch (e: any) {
    handleError(e, vm, `render`)
    // return error render result,
    // or previous vnode to prevent render error causing blank component
    /* istanbul ignore else */
    if (__DEV__ && vm.$options.renderError) {
      try {
        vnode = vm.$options.renderError.call(
          vm._renderProxy,
          vm.$createElement,
          e
        )
      } catch (e: any) {
        handleError(e, vm, `renderError`)
        vnode = vm._vnode
      }
    } else {
      vnode = vm._vnode
    }
  } finally {
    currentRenderingInstance = null
    setCurrentInstance()
  }
  // if the returned array contains only a single node, allow it
  if (isArray(vnode) && vnode.length === 1) {
    vnode = vnode[0]
  }
  // return empty vnode in case the render function errored out
  if (!(vnode instanceof VNode)) {
    if (__DEV__ && isArray(vnode)) {
      warn(
        'Multiple root nodes returned from render function. Render function ' +
          'should return a single root node.',
        vm
      )
    }
    vnode = createEmptyVNode()
  }
  // set parent
  vnode.parent = _parentVnode
  return vnode
}子组件通过 vm._render 生成 VNode 后,执行 vm._update 调用 __patch__ 进行 VNode 渲染。其中:
vm._vnode = vnode:vnode是通过vm._render()返回的组件渲染 VNodevm._vnode和vm.$vnode的关系就是一种父子关系,通过代码标识为vm._vnode.parent === vm.$vnodeactiveInstance: 作用是保存当前上下文的 Vue 实例,是lifecycle模块的全局变量。- 在调用 
createComponentInstanceForVnode(vnode, parent)方法创建组件 Vue 实例时,会从lifecycle模块中获取activeInstance,并作为parent参数传入。实际上,JavaScript 是一个单线程,Vue 整个初始化是一个深度遍历的过程,在实例化子组件的过程中,需要知道当前上下文的 Vue 实例,并把它作为子组件的父 Vue 实例。 - 子组件实例化的过程中,会调用 
this._init进行初始化。- 执行 
initInternalComponent(vm, options)。 合并options,将parent存储在vm.$options中。 - 执行 
initLifecycle(vm)。使用vm.$parent保留当前vm的父实例,并通过parent.$children.push(vm)把当前的vm存储到父实例的$children中。 
 - 执行 
 - 在执行 
vm._update过程中,会调用setActiveInstance将当前的vm赋值给activeInstance,同时,通过const prevActiveInstance = activeInstance使用prevActiveInstance保留上一次的activeInstance。实际上,prevActiveInstance和当前的vm是一个父子关系,当一个vm实例完成它的所有子数的patch或者update过程后,activeInstance会回到它的父实例,这样就能保证在createComponentInstanceForVnode(vnode, parent)整个深度遍历过程中,在实例子组件的时候能传入当前子组件的父 Vue 实例,并在this._init过程中,通过vm.$parent将这个父子关系保留。 
- 在调用 
 
【patch】过程:通过 vm._update 进行 VNode 渲染
// src\core\instance\lifecycle.ts
export let activeInstance: any = null
export function setActiveInstance(vm: Component) {
  const prevActiveInstance = activeInstance
  activeInstance = vm
  return () => {
    activeInstance = prevActiveInstance
  }
}
Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
  const vm: Component = this
  const prevEl = vm.$el
  const prevVnode = vm._vnode
  const restoreActiveInstance = setActiveInstance(vm)
  vm._vnode = vnode
  // Vue.prototype.__patch__ is injected in entry points
  // based on the rendering backend used.
  if (!prevVnode) {
    // initial render
    vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
  } else {
    // updates
    vm.$el = vm.__patch__(prevVnode, vnode)
  }
  restoreActiveInstance()
  // update __vue__ reference
  if (prevEl) {
    prevEl.__vue__ = null
  }
  if (vm.$el) {
    vm.$el.__vue__ = vm
  }
  // if parent is an HOC, update its $el as well
  if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {
    vm.$parent.$el = vm.$el
  }
  // updated hook is called by the scheduler to ensure that children are
  // updated in a parent's updated hook.
}在 __patch__ 过程中,实际调用辅助函数 createElm(vnode, insertedVnodeQueue, parentElm, refElm, nested, ownerArray, index) 渲染 VNode 为真实 DOM 。
在 patch 函数中,初始化的时候,通过 createElm(vnode, insertedVnodeQueue) 调用。在 createElm 函数中,vnode : 组件渲染的 vnode ,即: vm._vnode :
- 如果组件的根节点是普通元素,则 
vm._vnode为普通vnode。在createElm中createComponent(vnode, insertedVnodeQueue, parentElm, refElm)返回为false。先创建一个父节点占位符,然后再遍历所有的子 VNode 递归调用createElm。 - 在遍历过程中,如果遇到子 VNode 是一个组件 VNode,则调用 
createComponent创建组件 VNode 重新执行相关逻辑。这样通过一个递归的方式就可以完整地构建了整个组件树。 
完成组件的整个 patch 过程后,最后执行 insert(parentElm, vnode.elm, refElm) 完成组件的 DOM 插入,如果组件 patch 过程中又创建了子组件,那么 DOM 的插入顺序是 先子后父 。
【patch】过程:调用的 patch 函数
export function createPatchFunction(backend) {
  return function patch(oldVnode, vnode, hydrating, removeOnly) {
    if (isUndef(vnode)) {
      if (isDef(oldVnode)) invokeDestroyHook(oldVnode)
      return
    }
    let isInitialPatch = false
    const insertedVnodeQueue: any[] = []
    if (isUndef(oldVnode)) {
      // empty mount (likely as component), create new root element
      isInitialPatch = true
      createElm(vnode, insertedVnodeQueue)
    } else {
      const isRealElement = isDef(oldVnode.nodeType)
      if (!isRealElement && sameVnode(oldVnode, vnode)) {
        // patch existing root node
        patchVnode(oldVnode, vnode, insertedVnodeQueue, null, null, removeOnly)
      } else {
        if (isRealElement) {
          // mounting to a real element
          // check if this is server-rendered content and if we can perform
          // a successful hydration.
          if (oldVnode.nodeType === 1 && oldVnode.hasAttribute(SSR_ATTR)) {
            oldVnode.removeAttribute(SSR_ATTR)
            hydrating = true
          }
          if (isTrue(hydrating)) {
            if (hydrate(oldVnode, vnode, insertedVnodeQueue)) {
              invokeInsertHook(vnode, insertedVnodeQueue, true)
              return oldVnode
            } else if (__DEV__) {
              warn(
                'The client-side rendered virtual DOM tree is not matching ' +
                  'server-rendered content. This is likely caused by incorrect ' +
                  'HTML markup, for example nesting block-level elements inside ' +
                  '<p>, or missing <tbody>. Bailing hydration and performing ' +
                  'full client-side render.'
              )
            }
          }
          // either not server-rendered, or hydration failed.
          // create an empty node and replace it
          oldVnode = emptyNodeAt(oldVnode)
        }
        // replacing existing element
        const oldElm = oldVnode.elm
        const parentElm = nodeOps.parentNode(oldElm)
        // create new node
        createElm(
          vnode,
          insertedVnodeQueue,
          // extremely rare edge case: do not insert if old element is in a
          // leaving transition. Only happens when combining transition +
          // keep-alive + HOCs. (#4590)
          oldElm._leaveCb ? null : parentElm,
          nodeOps.nextSibling(oldElm)
        )
        // update parent placeholder node element, recursively
        if (isDef(vnode.parent)) {
          let ancestor = vnode.parent
          const patchable = isPatchable(vnode)
          while (ancestor) {
            for (let i = 0; i < cbs.destroy.length; ++i) {
              cbs.destroy[i](ancestor)
            }
            ancestor.elm = vnode.elm
            if (patchable) {
              for (let i = 0; i < cbs.create.length; ++i) {
                cbs.create[i](emptyNode, ancestor)
              }
              // #6513
              // invoke insert hooks that may have been merged by create hooks.
              // e.g. for directives that uses the "inserted" hook.
              const insert = ancestor.data.hook.insert
              if (insert.merged) {
                // start at index 1 to avoid re-invoking component mounted hook
                for (let i = 1; i < insert.fns.length; i++) {
                  insert.fns[i]()
                }
              }
            } else {
              registerRef(ancestor)
            }
            ancestor = ancestor.parent
          }
        }
        // destroy old node
        if (isDef(parentElm)) {
          removeVnodes([oldVnode], 0, 0)
        } else if (isDef(oldVnode.tag)) {
          invokeDestroyHook(oldVnode)
        }
      }
    }
    invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch)
    return vnode.elm
  }
}【patch】过程:调用的 patch 辅助函数 createElm
function createElm(
  vnode,
  insertedVnodeQueue,
  parentElm?: any,
  refElm?: any,
  nested?: any,
  ownerArray?: any,
  index?: any
) {
  if (isDef(vnode.elm) && isDef(ownerArray)) {
    // This vnode was used in a previous render!
    // now it's used as a new node, overwriting its elm would cause
    // potential patch errors down the road when it's used as an insertion
    // reference node. Instead, we clone the node on-demand before creating
    // associated DOM element for it.
    vnode = ownerArray[index] = cloneVNode(vnode)
  }
  vnode.isRootInsert = !nested // for transition enter check
  if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {
    return
  }
  const data = vnode.data
  const children = vnode.children
  const tag = vnode.tag
  if (isDef(tag)) {
    if (__DEV__) {
      if (data && data.pre) {
        creatingElmInVPre++
      }
      if (isUnknownElement(vnode, creatingElmInVPre)) {
        warn(
          'Unknown custom element: <' +
            tag +
            '> - did you ' +
            'register the component correctly? For recursive components, ' +
            'make sure to provide the "name" option.',
          vnode.context
        )
      }
    }
    vnode.elm = vnode.ns
      ? nodeOps.createElementNS(vnode.ns, tag)
      : nodeOps.createElement(tag, vnode)
    setScope(vnode)
    createChildren(vnode, children, insertedVnodeQueue)
    if (isDef(data)) {
      invokeCreateHooks(vnode, insertedVnodeQueue)
    }
    insert(parentElm, vnode.elm, refElm)
    if (__DEV__ && data && data.pre) {
      creatingElmInVPre--
    }
  } else if (isTrue(vnode.isComment)) {
    vnode.elm = nodeOps.createComment(vnode.text)
    insert(parentElm, vnode.elm, refElm)
  } else {
    vnode.elm = nodeOps.createTextNode(vnode.text)
    insert(parentElm, vnode.elm, refElm)
  }
}merge options : 合并配置
在进行 Vue 实例化的过程中,会执行 this._init (即:Vue.prototype._init)。该过程中会进行 merge options 合并配置。 new Vue 的过程通常有 2 种场景:
- 外部主动调用 
new Vue(options)实例化 Vue 对象 - 组件内部调用 
new Vue(options)实例化子组件 
【Vue 实例初始化】过程:调用 Vue.prototype._init 合并配置
// src\core\instance\init.ts
export function initMixin(Vue: typeof Component) {
  Vue.prototype._init = function (options?: Record<string, any>) {
    const vm: Component = this
    // a uid
    vm._uid = uid++
    let startTag, endTag
    /* istanbul ignore if */
    if (__DEV__ && config.performance && mark) {
      startTag = `vue-perf-start:${vm._uid}`
      endTag = `vue-perf-end:${vm._uid}`
      mark(startTag)
    }
    // a flag to mark this as a Vue instance without having to do instanceof
    // check
    vm._isVue = true
    // avoid instances from being observed
    vm.__v_skip = true
    // effect scope
    vm._scope = new EffectScope(true /* detached */)
    vm._scope._vm = true
    // merge options
    if (options && options._isComponent) {
      // optimize internal component instantiation
      // since dynamic options merging is pretty slow, and none of the
      // internal component options needs special treatment.
      initInternalComponent(vm, options as any)
    } else {
      vm.$options = mergeOptions(
        resolveConstructorOptions(vm.constructor as any),
        options || {},
        vm
      )
    }
    /* istanbul ignore else */
    if (__DEV__) {
      initProxy(vm)
    } else {
      vm._renderProxy = vm
    }
    // expose real self
    vm._self = vm
    initLifecycle(vm)
    initEvents(vm)
    initRender(vm)
    callHook(vm, 'beforeCreate', undefined, false /* setContext */)
    initInjections(vm) // resolve injections before data/props
    initState(vm)
    initProvide(vm) // resolve provide after data/props
    callHook(vm, 'created')
    /* istanbul ignore if */
    if (__DEV__ && config.performance && mark) {
      vm._name = formatComponentName(vm, false)
      mark(endTag)
      measure(`vue ${vm._name} init`, startTag, endTag)
    }
    if (vm.$options.el) {
      vm.$mount(vm.$options.el)
    }
  }
}外部主动调用合并配置
外部主动调用 new Vue(options) 的时候,会执行 this._init (即:Vue.prototype._init)中如下逻辑合并 options 。实际上是把 resolveConstructorOptions(vm.constructor) 的返回值和 options 合并。
vm.$options = mergeOptions(
  resolveConstructorOptions(vm.constructor),
  options || {},
  vm
)resolveConstructorOptions(vm.constructor) 返回为 vm.constructor.options,相当于 Vue.options 。
Vue.options 是在初始化 Vue 过程中,执行 initGlobalAPI(Vue) 的时候定义,其中:
通过
Vue.options = Object.create(null)创建空对象遍历
ASSET_TYPES在Vue.options上扩展component、directive、filter,相当于Vue.options.components = {} Vue.options.directives = {} Vue.options.filters = {}通过
Vue.options._base = Vue获取 Vue 的构造函数通过
extend(Vue.options.components, builtInComponents)将一些内置组件扩展到Vue.options.components上。目前 Vue 的内置组件包括<keep-alive>、<transition>和<transition-group>组件,这也就是在其他组件中使用<keep-alive>不需要注册的原因。
initGlobalAPI 函数
// src\shared\constants.ts
export const ASSET_TYPES = ['component', 'directive', 'filter'] as const
// src\core\global-api\index.ts
export function initGlobalAPI(Vue: GlobalAPI) {
  // config
  const configDef: Record<string, any> = {}
  configDef.get = () => config
  if (__DEV__) {
    configDef.set = () => {
      warn(
        'Do not replace the Vue.config object, set individual fields instead.'
      )
    }
  }
  Object.defineProperty(Vue, 'config', configDef)
  // exposed util methods.
  // NOTE: these are not considered part of the public API - avoid relying on
  // them unless you are aware of the risk.
  Vue.util = {
    warn,
    extend,
    mergeOptions,
    defineReactive
  }
  Vue.set = set
  Vue.delete = del
  Vue.nextTick = nextTick
  // 2.6 explicit observable API
  Vue.observable = <T>(obj: T): T => {
    observe(obj)
    return obj
  }
  Vue.options = Object.create(null)
  ASSET_TYPES.forEach(type => {
    Vue.options[type + 's'] = Object.create(null)
  })
  // this is used to identify the "base" constructor to extend all plain-object
  // components with in Weex's multi-instance scenarios.
  Vue.options._base = Vue
  extend(Vue.options.components, builtInComponents)
  initUse(Vue)
  initMixin(Vue)
  initExtend(Vue)
  initAssetRegisters(Vue)
}mergeOptions 函数主要功能是将 parent 和 child 这两个对象根据合并策略,合并成一个新对象并返回。其中,核心逻辑为:先递归将 extend 和 mixins 合并到 parent 上,然后遍历 parent,调用 mergeField ,然后再遍历 child,如果 key 不在 parent 的自身属性上,则调用 mergeField 函数。
对于 mergeField 函数,不同的 Key 有着不同的合并策略。以生命周期为例:
- 如果不存在 
childVal,就返回parentVal - 否则,再判断是否存在 
parentVal,如果存在就把childVal添加到parentVal后返回新数组 - 否则,返回 
childVal的数组 
所以,一旦 parent 和 child 都定义了相同的钩子函数,会把 2 个钩子函数合并成一个数组
mergeOptions 函数
// src\core\util\options.ts
/**
 * Merge two option objects into a new one.
 * Core utility used in both instantiation and inheritance.
 */
export function mergeOptions(
  parent: Record<string, any>,
  child: Record<string, any>,
  vm?: Component | null
): ComponentOptions {
  if (__DEV__) {
    checkComponents(child)
  }
  if (isFunction(child)) {
    // @ts-expect-error
    child = child.options
  }
  normalizeProps(child, vm)
  normalizeInject(child, vm)
  normalizeDirectives(child)
  // Apply extends and mixins on the child options,
  // but only if it is a raw options object that isn't
  // the result of another mergeOptions call.
  // Only merged options has the _base property.
  if (!child._base) {
    if (child.extends) {
      parent = mergeOptions(parent, child.extends, vm)
    }
    if (child.mixins) {
      for (let i = 0, l = child.mixins.length; i < l; i++) {
        parent = mergeOptions(parent, child.mixins[i], vm)
      }
    }
  }
  const options: ComponentOptions = {} as any
  let key
  for (key in parent) {
    mergeField(key)
  }
  for (key in child) {
    if (!hasOwn(parent, key)) {
      mergeField(key)
    }
  }
  function mergeField(key: any) {
    const strat = strats[key] || defaultStrat
    options[key] = strat(parent[key], child[key], vm, key)
  }
  return options
}
LIFECYCLE_HOOKS.forEach(hook => {
  strats[hook] = mergeLifecycleHook
})
/**
 * Hooks and props are merged as arrays.
 */
export function mergeLifecycleHook(
  parentVal: Array<Function> | null,
  childVal: Function | Array<Function> | null
): Array<Function> | null {
  const res = childVal
    ? parentVal
      ? parentVal.concat(childVal)
      : isArray(childVal)
      ? childVal
      : [childVal]
    : parentVal
  return res ? dedupeHooks(res) : res
}
// src\shared\constants.ts
export const LIFECYCLE_HOOKS = [
  'beforeCreate',
  'created',
  'beforeMount',
  'mounted',
  'beforeUpdate',
  'updated',
  'beforeDestroy',
  'destroyed',
  'activated',
  'deactivated',
  'errorCaptured',
  'serverPrefetch',
  'renderTracked',
  'renderTriggered'
] as const组件内部调用合并配置
组件的构造函数是通过 Vue.extend(extendOptions) 继承 Vue 的。其中,extendOptions 为定义的组件对象,会和 Vue.options 合并到 Sub.options 中。
Vue.extend
export function initExtend(Vue: GlobalAPI) {
  /**
   * Each instance constructor, including Vue, has a unique
   * cid. This enables us to create wrapped "child
   * constructors" for prototypal inheritance and cache them.
   */
  Vue.cid = 0
  let cid = 1
  /**
   * Class inheritance
   */
  Vue.extend = function (extendOptions: any): typeof Component {
    extendOptions = extendOptions || {}
    const Super = this
    const SuperId = Super.cid
    const cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {})
    if (cachedCtors[SuperId]) {
      return cachedCtors[SuperId]
    }
    const name =
      getComponentName(extendOptions) || getComponentName(Super.options)
    if (__DEV__ && name) {
      validateComponentName(name)
    }
    const Sub = function VueComponent(this: any, options: any) {
      this._init(options)
    } as unknown as typeof Component
    Sub.prototype = Object.create(Super.prototype)
    Sub.prototype.constructor = Sub
    Sub.cid = cid++
    Sub.options = mergeOptions(Super.options, extendOptions)
    Sub['super'] = Super
    // For props and computed properties, we define the proxy getters on
    // the Vue instances at extension time, on the extended prototype. This
    // avoids Object.defineProperty calls for each instance created.
    if (Sub.options.props) {
      initProps(Sub)
    }
    if (Sub.options.computed) {
      initComputed(Sub)
    }
    // allow further extension/mixin/plugin usage
    Sub.extend = Super.extend
    Sub.mixin = Super.mixin
    Sub.use = Super.use
    // create asset registers, so extended classes
    // can have their private assets too.
    ASSET_TYPES.forEach(function (type) {
      Sub[type] = Super[type]
    })
    // enable recursive self-lookup
    if (name) {
      Sub.options.components[name] = Sub
    }
    // keep a reference to the super options at extension time.
    // later at instantiation we can check if Super's options have
    // been updated.
    Sub.superOptions = Super.options
    Sub.extendOptions = extendOptions
    Sub.sealedOptions = extend({}, Sub.options)
    // cache constructor
    cachedCtors[SuperId] = Sub
    return Sub
  }
}子组件通过 createComponentInstanceForVnode 函数进行组件实例化,并执行实例的 this._init 方法,判断 options._isComponent 为 true 会执行 initInternalComponent(vm, options) 进行 options 合并。其中:
- 执行 
const opts = vm.$options = Object.create(vm.constructor.options)vm.constructor是子组件的构造函数Sub,相当于vm.$options = Object.create(Sub.options)
 - 然后,把实例化子组件传入的子组件父 VNode 实例 
parentVnode、 子组件的父 Vue 实例parent保存到vm.$options中。 - 同时,还保留了 
parentVnode配置中的propsData等其他属性 
initInternalComponent
// src\core\instance\init.ts
export function initInternalComponent(
  vm: Component,
  options: InternalComponentOptions
) {
  const opts = (vm.$options = Object.create((vm.constructor as any).options))
  // doing this because it's faster than dynamic enumeration.
  const parentVnode = options._parentVnode
  opts.parent = options.parent
  opts._parentVnode = parentVnode
  const vnodeComponentOptions = parentVnode.componentOptions!
  opts.propsData = vnodeComponentOptions.propsData
  opts._parentListeners = vnodeComponentOptions.listeners
  opts._renderChildren = vnodeComponentOptions.children
  opts._componentTag = vnodeComponentOptions.tag
  if (options.render) {
    opts.render = options.render
    opts.staticRenderFns = options.staticRenderFns
  }
}生命周期
Vue 实例在被创建之前会经过一系列的初始化过程。例如,需要设置数据监听、编译模板、挂载实例到 DOM 、在数据变化时更新 DOM 等。在执行的过程中,会运行生命周期钩子函数,供用户在一些特定的场景中执行相关代码。
执行生命周期函数都会调用 callHook 函数,在 lifecycle 模块中定义。
在 callHook 函数中,会根据传入的字符串 hook ,获取 vm.$options[hook] 对应的回调函数数组,然后遍历执行,执行的时候会将 vm 作为函数执行的上下文。
lifecycle 模块 callHook 函数
// src\core\instance\lifecycle.ts
export function callHook(
  vm: Component,
  hook: string,
  args?: any[],
  setContext = true
) {
  // #7573 disable dep collection when invoking lifecycle hooks
  pushTarget()
  const prev = currentInstance
  setContext && setCurrentInstance(vm)
  const handlers = vm.$options[hook]
  const info = `${hook} hook`
  if (handlers) {
    for (let i = 0, j = handlers.length; i < j; i++) {
      invokeWithErrorHandling(handlers[i], vm, args || null, vm, info)
    }
  }
  if (vm._hasHookEvent) {
    vm.$emit('hook:' + hook)
  }
  setContext && setCurrentInstance(prev)
  popTarget()
}beforeCreate & created
beforeCreate 和 created 函数在进行 Vue 实例化的过程中,在调用 this._init 方法(即:Vue.prototype._init 方法)进行初始化的时候执行的。
beforeCreate 和 created 生命周期钩子函数分别在 initState 前后执行。initState 函数会初始化 props 、 data 、methods 、watch 、 computed 等属性。beforeCreate 的生命钩子函数中,不能获取到 props 、 data 中定义的值,也不能调用 methods 中定义的函数。
beforeCreate 和 created 在执行的时候,并没有渲染 DOM ,也不能访问 DOM 。
一般来说,如果组件在加载的时候,需要与后端进行交互,将交互逻辑放在这两个钩子函数之后即可。
Vue.prototype._init(options) : 初始化 Vue
// src\core\instance\init.ts
export function initMixin(Vue: typeof Component) {
  Vue.prototype._init = function (options?: Record<string, any>) {
    const vm: Component = this
    // a uid
    vm._uid = uid++
    let startTag, endTag
    /* istanbul ignore if */
    if (__DEV__ && config.performance && mark) {
      startTag = `vue-perf-start:${vm._uid}`
      endTag = `vue-perf-end:${vm._uid}`
      mark(startTag)
    }
    // a flag to mark this as a Vue instance without having to do instanceof
    // check
    vm._isVue = true
    // avoid instances from being observed
    vm.__v_skip = true
    // effect scope
    vm._scope = new EffectScope(true /* detached */)
    vm._scope._vm = true
    // merge options
    if (options && options._isComponent) {
      // optimize internal component instantiation
      // since dynamic options merging is pretty slow, and none of the
      // internal component options needs special treatment.
      initInternalComponent(vm, options as any)
    } else {
      // 合并配置
      vm.$options = mergeOptions(
        resolveConstructorOptions(vm.constructor as any),
        options || {},
        vm
      )
    }
    /* istanbul ignore else */
    if (__DEV__) {
      initProxy(vm)
    } else {
      vm._renderProxy = vm
    }
    // expose real self
    vm._self = vm
    initLifecycle(vm) // 初始化生命周期
    initEvents(vm) // 初始化事件中心
    initRender(vm) // 初始化渲染
    callHook(vm, 'beforeCreate', undefined, false /* setContext */)
    initInjections(vm) // 初始化 inject - resolve injections before data/props
    initState(vm) // 初始化 props 、 methods 、 data 、 computed 、 watch
    initProvide(vm) // 初始化 provide - resolve provide after data/props
    callHook(vm, 'created')
    /* istanbul ignore if */
    if (__DEV__ && config.performance && mark) {
      vm._name = formatComponentName(vm, false)
      mark(endTag)
      measure(`vue ${vm._name} init`, startTag, endTag)
    }
    // 如果有 el 属性, 则调用 vm.$mount 方法挂载 vm
    // 目的是将模板渲染程最终的 DOM
    if (vm.$options.el) {
      vm.$mount(vm.$options.el)
    }
  }
}beforeMount & mounted
beforeMount 函数发生在 Vue 实例挂载 DOM 之前,在执行公共 $mount 方法中,最终调用 mountComponent 函数中执行。
在执行 vm._render() 函数渲染 VNode 之前,执行了 beforeMount 钩子函数,在执行完 vm._update() 把 VNode patch 到真实 DOM 后,执行 mounted 钩子函数。
在执行 mounted 钩子函数的时候,会进行判断。如果 vm.$vnode == null ,则表示不是组件的初始化过程,是通过外部 new Vue 的初始化过程。
对于组件 VNode patch 到 DOM 的过程中,会执行 invokeInsertHook 函数,把 insertedVnodeQueue 里保存的钩子函数依次执行一遍。
invokeInsertHook 函数会执行 insert 钩子函数,每个子组件都是在这个钩子函数中执行 mounted 钩子函数。insertedVnodeQueue 的添加顺序是先子后父,对于同步渲染的子组件而言,mounted 钩子函数的执行顺序也是先子后父。
【$mount 挂载】过程:调用的 mountComponent 方法
// src\core\instance\lifecycle.ts
export function mountComponent(
  vm: Component,
  el: Element | null | undefined,
  hydrating?: boolean
): Component {
  vm.$el = el
  if (!vm.$options.render) {
    // @ts-expect-error invalid type
    vm.$options.render = createEmptyVNode
    if (__DEV__) {
      /* istanbul ignore if */
      if (
        (vm.$options.template && vm.$options.template.charAt(0) !== '#') ||
        vm.$options.el ||
        el
      ) {
        warn(
          'You are using the runtime-only build of Vue where the template ' +
            'compiler is not available. Either pre-compile the templates into ' +
            'render functions, or use the compiler-included build.',
          vm
        )
      } else {
        warn(
          'Failed to mount component: template or render function not defined.',
          vm
        )
      }
    }
  }
  callHook(vm, 'beforeMount')
  let updateComponent
  /* istanbul ignore if */
  if (__DEV__ && config.performance && mark) {
    updateComponent = () => {
      const name = vm._name
      const id = vm._uid
      const startTag = `vue-perf-start:${id}`
      const endTag = `vue-perf-end:${id}`
      mark(startTag)
      const vnode = vm._render()
      mark(endTag)
      measure(`vue ${name} render`, startTag, endTag)
      mark(startTag)
      vm._update(vnode, hydrating)
      mark(endTag)
      measure(`vue ${name} patch`, startTag, endTag)
    }
  } else {
    updateComponent = () => {
      vm._update(vm._render(), hydrating)
    }
  }
  const watcherOptions: WatcherOptions = {
    before() {
      if (vm._isMounted && !vm._isDestroyed) {
        callHook(vm, 'beforeUpdate')
      }
    }
  }
  if (__DEV__) {
    watcherOptions.onTrack = e => callHook(vm, 'renderTracked', [e])
    watcherOptions.onTrigger = e => callHook(vm, 'renderTriggered', [e])
  }
  // we set this to vm._watcher inside the watcher's constructor
  // since the watcher's initial patch may call $forceUpdate (e.g. inside child
  // component's mounted hook), which relies on vm._watcher being already defined
  new Watcher(
    vm,
    updateComponent,
    noop,
    watcherOptions,
    true /* isRenderWatcher */
  )
  hydrating = false
  // flush buffer for flush: "pre" watchers queued in setup()
  const preWatchers = vm._preWatchers
  if (preWatchers) {
    for (let i = 0; i < preWatchers.length; i++) {
      preWatchers[i].run()
    }
  }
  // manually mounted instance, call mounted on self
  // mounted is called for render-created child components in its inserted hook
  if (vm.$vnode == null) {
    vm._isMounted = true
    callHook(vm, 'mounted')
  }
  return vm
}【patch】过程:调用 invokeInsertHook 函数
function invokeInsertHook(vnode, queue, initial) {
  // delay insert hooks for component root nodes, invoke them after the
  // element is really inserted
  if (isTrue(initial) && isDef(vnode.parent)) {
    vnode.parent.data.pendingInsert = queue
  } else {
    for (let i = 0; i < queue.length; ++i) {
      queue[i].data.hook.insert(queue[i])
    }
  }
}beforeUpdate & updated
beforeUpdate 和 updated 的钩子函数执行时机都应该是在数据更新的时候。
beforeUpdate 的执行时机是在调用 mountComponent 函数中,渲染 Watcher 的 before 函数中调用。在组件 mounted 之后,才会调用这个钩子函数。
update 的执行时机是在 flushSchedulerQueue 函数调用的时候。其中,updatedQueue 是更新了的 watcher 数组,在调用 callUpdatedHooks 函数中,对 updatedQueue 中的数组进行遍历,只有满足当前 watcher 为 vm._watcher 以及组件已完成 mounted,才会调用 updated 钩子函数。
- 在组件 
mount的过程中,会在mountComponent函数执行时,会实例化一个渲染的Watcher去监听vm上的数据变化重新渲染。 - 在实例化 
Watcher的过程中,在它的构造函数里会判断isRenderWatcher,把当前watcher的实例赋值给vm._watcher,同时,把当前watcher实例 push 到vm._watchers中。 vm._watcher是专门用来监听vm上数据变化然后重新渲染的,所以它是一个渲染相关的watcher,因此在callUpdatedHooks函数中,只有vm._watcher的回调执行完毕后,才会执行updated钩子函数。
flushSchedulerQueue 函数
// src\core\observer\scheduler.ts
/**
 * Flush both queues and run the watchers.
 */
function flushSchedulerQueue() {
  currentFlushTimestamp = getNow()
  flushing = true
  let watcher, id
  // Sort queue before flush.
  // This ensures that:
  // 1. Components are updated from parent to child. (because parent is always
  //    created before the child)
  // 2. A component's user watchers are run before its render watcher (because
  //    user watchers are created before the render watcher)
  // 3. If a component is destroyed during a parent component's watcher run,
  //    its watchers can be skipped.
  queue.sort(sortCompareFn)
  // do not cache length because more watchers might be pushed
  // as we run existing watchers
  for (index = 0; index < queue.length; index++) {
    watcher = queue[index]
    if (watcher.before) {
      watcher.before()
    }
    id = watcher.id
    has[id] = null
    watcher.run()
    // in dev build, check and stop circular updates.
    if (__DEV__ && has[id] != null) {
      circular[id] = (circular[id] || 0) + 1
      if (circular[id] > MAX_UPDATE_COUNT) {
        warn(
          'You may have an infinite update loop ' +
            (watcher.user
              ? `in watcher with expression "${watcher.expression}"`
              : `in a component render function.`),
          watcher.vm
        )
        break
      }
    }
  }
  // keep copies of post queues before resetting state
  const activatedQueue = activatedChildren.slice()
  const updatedQueue = queue.slice()
  resetSchedulerState()
  // call component updated and activated hooks
  callActivatedHooks(activatedQueue)
  callUpdatedHooks(updatedQueue)
  // devtool hook
  /* istanbul ignore if */
  if (devtools && config.devtools) {
    devtools.emit('flush')
  }
}
function callUpdatedHooks(queue: Watcher[]) {
  let i = queue.length
  while (i--) {
    const watcher = queue[i]
    const vm = watcher.vm
    if (vm && vm._watcher === watcher && vm._isMounted && !vm._isDestroyed) {
      callHook(vm, 'updated')
    }
  }
}beforeDestroy & destroyed
beforeDestroy 和 destroyed 钩子函数的执行时机在组件销毁的阶段,最终会调用 $destroy 方法。
beforeDestroy 钩子函数的执行时机是在 $destroy 函数执行最开始的地方,接着执行了一系列的销毁动作:从 parent 的 $children 中删掉自身、删除 watcher、删除当前渲染的 VNode 执行销毁钩子函数等,执行完毕后再调用 destroy 钩子函函数
$destroy 的执行过程中,它又会执行 vm.__patch__(vm._vnode, null) 触发它子组件的销毁钩子函数,一层层的递归调用,所以 destroy 钩子函数执行顺序是先子后父,和 mounted 过程一样。
Vue.prototype.$destroy
Vue.prototype.$destroy = function () {
  const vm: Component = this
  if (vm._isBeingDestroyed) {
    return
  }
  callHook(vm, 'beforeDestroy')
  vm._isBeingDestroyed = true
  // remove self from parent
  const parent = vm.$parent
  if (parent && !parent._isBeingDestroyed && !vm.$options.abstract) {
    remove(parent.$children, vm)
  }
  // teardown scope. this includes both the render watcher and other
  // watchers created
  vm._scope.stop()
  // remove reference from data ob
  // frozen object may not have observer.
  if (vm._data.__ob__) {
    vm._data.__ob__.vmCount--
  }
  // call the last hook...
  vm._isDestroyed = true
  // invoke destroy hooks on current rendered tree
  vm.__patch__(vm._vnode, null)
  // fire destroyed hook
  callHook(vm, 'destroyed')
  // turn off all instance listeners.
  vm.$off()
  // remove __vue__ reference
  if (vm.$el) {
    vm.$el.__vue__ = null
  }
  // release circular reference (#6759)
  if (vm.$vnode) {
    vm.$vnode.parent = null
  }
}组件注册
全局注册
注册全局属性,可通过 Vue.component(tagName, options) 进行注册。
Vue.component 是在初始化 Vue 过程中,调用 initGlobalAPI(Vue) 函数,执行 initAssetRegisters 的时候定义。在 initAssetRegisters 函数中:
- 如果 
type是component且definition是一个对象的话,通过this.options._base.extend,相当于Vue.extend把这个对象转换成一个继承于 Vue 的构造函数。 - 最后通过 
this.options[type + 's'][id] = definition把它挂载到Vue.options.components上。 
初始化 Vue 过程中,调用的 initGlobalAPI 函数
// src\core\global-api\index.ts
export function initGlobalAPI(Vue: GlobalAPI) {
  // config
  const configDef: Record<string, any> = {}
  configDef.get = () => config
  if (__DEV__) {
    configDef.set = () => {
      warn(
        'Do not replace the Vue.config object, set individual fields instead.'
      )
    }
  }
  Object.defineProperty(Vue, 'config', configDef)
  // exposed util methods.
  // NOTE: these are not considered part of the public API - avoid relying on
  // them unless you are aware of the risk.
  Vue.util = {
    warn,
    extend,
    mergeOptions,
    defineReactive
  }
  Vue.set = set
  Vue.delete = del
  Vue.nextTick = nextTick
  // 2.6 explicit observable API
  Vue.observable = <T>(obj: T): T => {
    observe(obj)
    return obj
  }
  Vue.options = Object.create(null)
  ASSET_TYPES.forEach(type => {
    Vue.options[type + 's'] = Object.create(null)
  })
  // this is used to identify the "base" constructor to extend all plain-object
  // components with in Weex's multi-instance scenarios.
  Vue.options._base = Vue
  extend(Vue.options.components, builtInComponents)
  initUse(Vue)
  initMixin(Vue)
  initExtend(Vue)
  initAssetRegisters(Vue)
}在调用 initGlobalAPI 函数中,执行的 initAssetRegisters 函数
// src\shared\constants.ts
export const ASSET_TYPES = ['component', 'directive', 'filter'] as const
// src\core\global-api\assets.ts
export function initAssetRegisters(Vue: GlobalAPI) {
  /**
   * Create asset registration methods.
   */
  ASSET_TYPES.forEach(type => {
    // @ts-expect-error function is not exact same type
    Vue[type] = function (
      id: string,
      definition?: Function | Object
    ): Function | Object | void {
      if (!definition) {
        return this.options[type + 's'][id]
      } else {
        /* istanbul ignore if */
        if (__DEV__ && type === 'component') {
          validateComponentName(id)
        }
        if (type === 'component' && isPlainObject(definition)) {
          // @ts-expect-error
          definition.name = definition.name || id
          definition = this.options._base.extend(definition)
        }
        if (type === 'directive' && isFunction(definition)) {
          definition = { bind: definition, update: definition }
        }
        this.options[type + 's'][id] = definition
        return definition
      }
    }
  })
}每个组件的创建都是通过 Vue.extend 基础而来的。在 Vue.extend 方法中,会执行 Sub.options = mergeOptions(Super.options, extendOptions),把 Vue.options 合并到 Super.options (即:组件的 options)。然后,在组件的实例化阶段,执行 merge options 逻辑,把 Sub.options.components 合并到 vm.$options.components 上。
在创建 vnode 过程中,会通过 createElement 方法执行 _createElement 方法。在 _createElement 方法中,会执行判断逻辑 isDef(Ctor = resolveAsset(context.$options, 'components', tag)) 。
在 resolveAsset 方法中 :
先通过
const assets = options[type]获取assets再获取
assets[id],获取规则如下:- 直接使用 
id,获取assets[id] - 如果不存在,则将 
id变成驼峰的形式,获取assets[id] - 如果仍然不存在,则在驼峰的基础上,将首字母再变成大写的形式,虎丘 
assets[id] - 如果仍然不存在,则报错
 
该获取规则,标识在使用
Vue.component(id, definition)全局注册组件时,id可以是连字符、驼峰或首字母大写的形式- 直接使用 
 
通过调用 resolveAsset(context.$options, 'components', tag) (即获取 vm.$options.components[tag])获取组件的构造函数,并作为 createComponent 的钩子的参数。
【创建 vnode】过程:通过 createElement 方法执行的 _createElement 方法
export function _createElement(
  context: Component,
  tag?: string | Component | Function | Object,
  data?: VNodeData,
  children?: any,
  normalizationType?: number
): VNode | Array<VNode> {
  if (isDef(data) && isDef((data as any).__ob__)) {
    __DEV__ &&
      warn(
        `Avoid using observed data object as vnode data: ${JSON.stringify(
          data
        )}\n` + 'Always create fresh vnode data objects in each render!',
        context
      )
    return createEmptyVNode()
  }
  // object syntax in v-bind
  if (isDef(data) && isDef(data.is)) {
    tag = data.is
  }
  if (!tag) {
    // in case of component :is set to falsy value
    return createEmptyVNode()
  }
  // warn against non-primitive key
  if (__DEV__ && isDef(data) && isDef(data.key) && !isPrimitive(data.key)) {
    warn(
      'Avoid using non-primitive value as key, ' +
        'use string/number value instead.',
      context
    )
  }
  // support single function children as default scoped slot
  if (isArray(children) && isFunction(children[0])) {
    data = data || {}
    data.scopedSlots = { default: children[0] }
    children.length = 0
  }
  if (normalizationType === ALWAYS_NORMALIZE) {
    children = normalizeChildren(children)
  } else if (normalizationType === SIMPLE_NORMALIZE) {
    children = simpleNormalizeChildren(children)
  }
  let vnode, ns
  if (typeof tag === 'string') {
    let Ctor
    ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag)
    if (config.isReservedTag(tag)) {
      // platform built-in elements
      if (
        __DEV__ &&
        isDef(data) &&
        isDef(data.nativeOn) &&
        data.tag !== 'component'
      ) {
        warn(
          `The .native modifier for v-on is only valid on components but it was used on <${tag}>.`,
          context
        )
      }
      vnode = new VNode(
        config.parsePlatformTagName(tag),
        data,
        children,
        undefined,
        undefined,
        context
      )
    } else if (
      (!data || !data.pre) &&
      isDef((Ctor = resolveAsset(context.$options, 'components', tag)))
    ) {
      // component
      vnode = createComponent(Ctor, data, context, children, tag)
    } else {
      // unknown or unlisted namespaced elements
      // check at runtime because it may get assigned a namespace when its
      // parent normalizes children
      vnode = new VNode(tag, data, children, undefined, undefined, context)
    }
  } else {
    // direct component options / constructor
    vnode = createComponent(tag as any, data, context, children)
  }
  if (isArray(vnode)) {
    return vnode
  } else if (isDef(vnode)) {
    if (isDef(ns)) applyNS(vnode, ns)
    if (isDef(data)) registerDeepBindings(data)
    return vnode
  } else {
    return createEmptyVNode()
  }
}【创建 vnode】过程:在 _createElement 方法中,调用的 resolveAsset 方法
// src\core\util\options.ts
/**
 * Resolve an asset.
 * This function is used because child instances need access
 * to assets defined in its ancestor chain.
 */
export function resolveAsset(
  options: Record<string, any>,
  type: string,
  id: string,
  warnMissing?: boolean
): any {
  /* istanbul ignore if */
  if (typeof id !== 'string') {
    return
  }
  const assets = options[type]
  // check local registration variations first
  if (hasOwn(assets, id)) return assets[id]
  const camelizedId = camelize(id)
  if (hasOwn(assets, camelizedId)) return assets[camelizedId]
  const PascalCaseId = capitalize(camelizedId)
  if (hasOwn(assets, PascalCaseId)) return assets[PascalCaseId]
  // fallback to prototype chain
  const res = assets[id] || assets[camelizedId] || assets[PascalCaseId]
  if (__DEV__ && warnMissing && !res) {
    warn('Failed to resolve ' + type.slice(0, -1) + ': ' + id)
  }
  return res
}局部注册
每个组件的创建都是通过 Vue.extend 基础而来的。在 Vue.extend 方法中,会执行 Sub.options = mergeOptions(Super.options, extendOptions),把 Vue.options 合并到 Super.options (即:组件的 options)。然后,在组件的实例化阶段,执行 merge options 逻辑,把 Sub.options.components 合并到 vm.$options.components 上。这样可以在执行 resolveAsset 函数的时候,获取到这个组件的构造函数,并作为 createComponent 的参数。
局部注册和全局注册不同的,全局注册是扩展到 Vue.options 下,所以在所有组件创建的过程中,都会从全局的 Vue.options.components 扩展到当前组件的 vm.$options.components 下,这就是全局注册的组件能被任意使用的原因。
异步组件
在大型应用中,为了减少首屏代码体积,会把一些非首屏的组件设计成异步组件,按需加载。主要分为以下三种异步组件。
// 普通函数异步组件
Vue.component('async-example', function (resolve, reject) {
   // 这个特殊的 require 语法告诉 webpack,自动将编译后的代码分割成不同的块
   // 这些块将通过 Ajax 请求自动下载。
   require(['./my-async-component'], resolve)
})
// Promise 异步组件
Vue.component(
  'async-webpack-example',
  // 该 `import` 函数返回一个 `Promise` 对象。
  () => import('./my-async-component')
)
// 高级异步组件
const AsyncComp = () => ({
  // 需要加载的组件。应当是一个 Promise
  component: import('./MyComp.vue'),
  // 加载中应当渲染的组件
  loading: LoadingComp,
  // 出错时渲染的组件
  error: ErrorComp,
  // 渲染加载中组件前的等待时间。默认:200ms。
  delay: 200,
  // 最长等待时间。超出此时间则渲染错误组件。默认:Infinity
  timeout: 3000
})
Vue.component('async-example', AsyncComp)在 render 过程中,通过 createComponent 创建组件 VNode 。
在异步组件场景下,createComponent(Ctor, data, context, children, tag) 中,Ctor 传入的是一个函数,并不会执行 Vue.extend 逻辑,因此它的 cid 是 undefined,进入了异步组件创建的处理逻辑,执行 Ctor = resolveAsyncComponent(asyncFactory, baseCtor) 。
【render】过程:通过 createComponent 创建 VNode
export function createComponent(
  Ctor: typeof Component | Function | ComponentOptions | void,
  data: VNodeData | undefined,
  context: Component,
  children?: Array<VNode>,
  tag?: string
): VNode | Array<VNode> | void {
  if (isUndef(Ctor)) {
    return
  }
  const baseCtor = context.$options._base
  // plain options object: turn it into a constructor
  if (isObject(Ctor)) {
    Ctor = baseCtor.extend(Ctor as typeof Component)
  }
  // if at this stage it's not a constructor or an async component factory,
  // reject.
  if (typeof Ctor !== 'function') {
    if (__DEV__) {
      warn(`Invalid Component definition: ${String(Ctor)}`, context)
    }
    return
  }
  // async component
  let asyncFactory
  // @ts-expect-error
  if (isUndef(Ctor.cid)) {
    asyncFactory = Ctor
    Ctor = resolveAsyncComponent(asyncFactory, baseCtor)
    if (Ctor === undefined) {
      // return a placeholder node for async component, which is rendered
      // as a comment node but preserves all the raw information for the node.
      // the information will be used for async server-rendering and hydration.
      return createAsyncPlaceholder(asyncFactory, data, context, children, tag)
    }
  }
  data = data || {}
  // resolve constructor options in case global mixins are applied after
  // component constructor creation
  resolveConstructorOptions(Ctor as typeof Component)
  // transform component v-model data into props & events
  if (isDef(data.model)) {
    // @ts-expect-error
    transformModel(Ctor.options, data)
  }
  // extract props
  // @ts-expect-error
  const propsData = extractPropsFromVNodeData(data, Ctor, tag)
  // functional component
  // @ts-expect-error
  if (isTrue(Ctor.options.functional)) {
    return createFunctionalComponent(
      Ctor as typeof Component,
      propsData,
      data,
      context,
      children
    )
  }
  // extract listeners, since these needs to be treated as
  // child component listeners instead of DOM listeners
  const listeners = data.on
  // replace with listeners with .native modifier
  // so it gets processed during parent component patch.
  data.on = data.nativeOn
  // @ts-expect-error
  if (isTrue(Ctor.options.abstract)) {
    // abstract components do not keep anything
    // other than props & listeners & slot
    // work around flow
    const slot = data.slot
    data = {}
    if (slot) {
      data.slot = slot
    }
  }
  // install component management hooks onto the placeholder node
  installComponentHooks(data)
  // return a placeholder vnode
  // @ts-expect-error
  const name = getComponentName(Ctor.options) || tag
  const vnode = new VNode(
    // @ts-expect-error
    `vue-component-${Ctor.cid}${name ? `-${name}` : ''}`,
    data,
    undefined,
    undefined,
    undefined,
    context,
    // @ts-expect-error
    { Ctor, propsData, listeners, tag, children },
    asyncFactory
  )
  return vnode
}创建异步组件: resolveAsyncComponent 函数
export function resolveAsyncComponent(
  factory: { (...args: any[]): any; [keye: string]: any },
  baseCtor: typeof Component
): typeof Component | void {
  if (isTrue(factory.error) && isDef(factory.errorComp)) {
    return factory.errorComp
  }
  if (isDef(factory.resolved)) {
    return factory.resolved
  }
  const owner = currentRenderingInstance
  if (owner && isDef(factory.owners) && factory.owners.indexOf(owner) === -1) {
    // already pending
    factory.owners.push(owner)
  }
  if (isTrue(factory.loading) && isDef(factory.loadingComp)) {
    return factory.loadingComp
  }
  if (owner && !isDef(factory.owners)) {
    const owners = (factory.owners = [owner])
    let sync = true
    let timerLoading: number | null = null
    let timerTimeout: number | null = null
    owner.$on('hook:destroyed', () => remove(owners, owner))
    const forceRender = (renderCompleted: boolean) => {
      for (let i = 0, l = owners.length; i < l; i++) {
        owners[i].$forceUpdate()
      }
      if (renderCompleted) {
        owners.length = 0
        if (timerLoading !== null) {
          clearTimeout(timerLoading)
          timerLoading = null
        }
        if (timerTimeout !== null) {
          clearTimeout(timerTimeout)
          timerTimeout = null
        }
      }
    }
    const resolve = once((res: Object | Component) => {
      // cache resolved
      factory.resolved = ensureCtor(res, baseCtor)
      // invoke callbacks only if this is not a synchronous resolve
      // (async resolves are shimmed as synchronous during SSR)
      if (!sync) {
        forceRender(true)
      } else {
        owners.length = 0
      }
    })
    const reject = once(reason => {
      __DEV__ &&
        warn(
          `Failed to resolve async component: ${String(factory)}` +
            (reason ? `\nReason: ${reason}` : '')
        )
      if (isDef(factory.errorComp)) {
        factory.error = true
        forceRender(true)
      }
    })
    const res = factory(resolve, reject)
    if (isObject(res)) {
      if (isPromise(res)) {
        // () => Promise
        if (isUndef(factory.resolved)) {
          res.then(resolve, reject)
        }
      } else if (isPromise(res.component)) {
        res.component.then(resolve, reject)
        if (isDef(res.error)) {
          factory.errorComp = ensureCtor(res.error, baseCtor)
        }
        if (isDef(res.loading)) {
          factory.loadingComp = ensureCtor(res.loading, baseCtor)
          if (res.delay === 0) {
            factory.loading = true
          } else {
            // @ts-expect-error NodeJS timeout type
            timerLoading = setTimeout(() => {
              timerLoading = null
              if (isUndef(factory.resolved) && isUndef(factory.error)) {
                factory.loading = true
                forceRender(false)
              }
            }, res.delay || 200)
          }
        }
        if (isDef(res.timeout)) {
          // @ts-expect-error NodeJS timeout type
          timerTimeout = setTimeout(() => {
            timerTimeout = null
            if (isUndef(factory.resolved)) {
              reject(__DEV__ ? `timeout (${res.timeout}ms)` : null)
            }
          }, res.timeout)
        }
      }
    }
    sync = false
    // return in case resolved synchronously
    return factory.loading ? factory.loadingComp : factory.resolved
  }
}普通函数异步组件
// 普通函数异步组件
Vue.component('async-example', function (resolve, reject) {
   // 这个特殊的 require 语法告诉 webpack,自动将编译后的代码分割成不同的块
   // 这些块将通过 Ajax 请求自动下载。
   require(['./my-async-component'], resolve)
})针对普通函数的情况,在 resolveAsyncComponent 创建异步组件函数中:
对当前渲染的实例(
currentRenderingInstance,从render模块中获取)进行判断,是考虑到多个地方同时初始化一个异步组件,实际应该只加载一次。进入实际加载逻辑,定义了
forceRender、resolve和reject函数,其中resolve和reject函数使用once函数进行了包装,确保只执行一次。forceRender函数const forceRender = (renderCompleted: boolean) => { for (let i = 0, l = owners.length; i < l; i++) { owners[i].$forceUpdate() } if (renderCompleted) { owners.length = 0 if (timerLoading !== null) { clearTimeout(timerLoading) timerLoading = null } if (timerTimeout !== null) { clearTimeout(timerTimeout) timerTimeout = null } } }resolve函数const resolve = once((res: Object | Component) => { // cache resolved factory.resolved = ensureCtor(res, baseCtor) // invoke callbacks only if this is not a synchronous resolve // (async resolves are shimmed as synchronous during SSR) if (!sync) { forceRender(true) } else { owners.length = 0 } })reject函数const reject = once(reason => { __DEV__ && warn( `Failed to resolve async component: ${String(factory)}` + (reason ? `\nReason: ${reason}` : '') ) if (isDef(factory.errorComp)) { factory.error = true forceRender(true) } })once函数接收一个函数参数,并返回一个新的函数。利用闭包和标志位保证通过once包装的函数只执行一次。/** * Ensure a function is called only once. */ export function once<T extends (...args: any[]) => any>(fn: T): T { let called = false return function () { if (!called) { called = true fn.apply(this, arguments as any) } } as any }
通过执行
const res = factory(resolve, reject)逻辑,执行定义普通函数异步组件的工厂函数,同时将resolve和reject函数作为参数传入。异步组件的工厂函数通常会发送请求加载异步组件的 JavaScript 文件,获取到定义的对象res后,执行resolve(res)逻辑。执行定义的
resolve(res)函数,其中:执行
factory.resolved = ensureCtor(res, baseCtor)逻辑。ensureCtor函数保证能找到异步组件 JavaScript 定义的组件对象,并且如果是一个普通对象,则调用Vue.extend将其转换成一个组件的构造函数。function ensureCtor(comp: any, base) { if (comp.__esModule || (hasSymbol && comp[Symbol.toStringTag] === 'Module')) { comp = comp.default } return isObject(comp) ? base.extend(comp) : comp }对
sync标识进行判断,在该场景下sync为false,则会执行forceRender函数
执行
forceRender函数,遍历factory.contexts获取每一个调用异步组件的实例vm,执行vm.$forceUpdate()方法,其定义在lifecycle模块中。在
$forceUpdate()方法中,调用渲染watcher的update方法,让渲染watcher对应的回调函数执行,触发组件的重新渲染。因为 Vue 通常是数据驱动视图重新渲染,在整个异步组件加载过程中是没有数据变化的,所以通过执行$forceUpdate()强制组件重新渲染一次。// src\core\instance\lifecycle.ts Vue.prototype.$forceUpdate = function () { const vm: Component = this if (vm._watcher) { vm._watcher.update() } }
Promise 异步组件
// Promise 异步组件
Vue.component(
  'async-webpack-example',
  // 该 `import` 函数返回一个 `Promise` 对象。
  () => import('./my-async-component')
)webpack 2+ 支持了异步加载的语法糖:() => import('./my-async-component')
当在 resolveAsyncComponent 创建异步组件函数中,执行 const res = factory(resolve, reject) ,得到的返回值就是 import('./my-async-component') 的返回值,是一个 Promise 对象。进入后续判断逻辑,满足 isPromise(res) ,执行如下逻辑:
if (isUndef(factory.resolved)) {
  res.then(resolve, reject)
}- 组件异步加载成功,则执行在 
resolveAsyncComponent函数中定义的resolve(res)函数 - 组件异步加载失败,则执行在 
resolveAsyncComponent函数中定义的reject(res)函数 
高级异步组件
高级异步组件支持设置 loading 组件与 error 组件,用于处理组件异步加载过程中与加载失败的场景。
// 高级异步组件
const AsyncComp = () => ({
  // 需要加载的组件。应当是一个 Promise
  component: import('./MyComp.vue'),
  // 加载中应当渲染的组件
  loading: LoadingComp,
  // 出错时渲染的组件
  error: ErrorComp,
  // 渲染加载中组件前的等待时间。默认:200ms。
  delay: 200,
  // 最长等待时间。超出此时间则渲染错误组件。默认:Infinity
  timeout: 3000
})
Vue.component('async-example', AsyncComp)当在 resolveAsyncComponent 创建异步组件函数中,执行 const res = factory(resolve, reject) ,得到的返回值为定义的组件对象。进入后续判断逻辑,满足 isPromise(res.component),执行 res.component.then(resolve, reject)。
- 组件异步加载成功,则执行在 
resolveAsyncComponent函数中定义的resolve(res)函数 - 组件异步加载失败,则执行在 
resolveAsyncComponent函数中定义的reject(res)函数 
因为异步组件加载是一个异步过程,同时,又会同步执行在 resolveAsyncComponent 中的如下逻辑:
- 判断 
res.error是否定义了error组件。如果有的话,则赋值给factory.errorComp。 - 判断 
res.loading是否定义了loading组件,如果有的话,则赋值给factory.loadingComp。 - 如果设置了 
res.delay且为0,则设置factory.loading = true,否则,延时delay的时间执行 - 最后,判断 
res.timeout,如果配置了该项,则在res.timeout时间后,如果组件没有成功加载,执行reject 
在 resolveAsyncComponent 函数的最后,设置 sync = false ,同时执行 factory.loading ? factory.loadingComp : factory.resolved 。
- 如果 
delay配置为0,则这次直接渲染loading组件 - 否则,则延时 
delay执行forceRender,会再一次执行到resolveAsyncComponent 
此时,会存在几种情况,按照逻辑的执行顺序,对不同情况进行判断:
异步组件加载失败,会执行
reject函数。const reject = once(reason => { __DEV__ && warn( `Failed to resolve async component: ${String(factory)}` + (reason ? `\nReason: ${reason}` : '') ) if (isDef(factory.errorComp)) { factory.error = true forceRender(true) } })此时,会把
factory.error设置为true,同时执行forceRender()再次执行到resolveAsyncComponent,会在该方法中执行到以下逻辑,返回factory.errorComp,直接渲染error组件if (isTrue(factory.error) && isDef(factory.errorComp)) { return factory.errorComp }异步组件加载成功,会执行
resolve函数const resolve = once((res: Object | Component) => { // cache resolved factory.resolved = ensureCtor(res, baseCtor) // invoke callbacks only if this is not a synchronous resolve // (async resolves are shimmed as synchronous during SSR) if (!sync) { forceRender(true) } else { owners.length = 0 } })此时,会把加载结果缓存到
factory.resolved中,这个时候因为sync已经为false,则执行forceRender()再次执行到resolveAsyncComponent,会在该方法中执行到以下逻辑,返回factory.resolved,直接渲染成功加载的组件if (isDef(factory.resolved)) { return factory.resolved }异步组件加载中,未成功返回,会执行到以下逻辑,返回
factory.loadingComp,渲染loading组件if (isTrue(factory.loading) && isDef(factory.loadingComp)) { return factory.loadingComp }异步组件加载超时,会进入
reject逻辑,之后逻辑和加载失败一样,渲染error组件。
异步组件 patch
在 createComponent 创建组件 VNode 方法中,通过 resolveAsyncComponent 函数完成异步组件创建后,会对返回值进行逻辑处理。
【render】过程:通过的 createComponent 方法创建 VNode
export function createComponent(
  Ctor: typeof Component | Function | ComponentOptions | void,
  data: VNodeData | undefined,
  context: Component,
  children?: Array<VNode>,
  tag?: string
): VNode | Array<VNode> | void {
  if (isUndef(Ctor)) {
    return
  }
  const baseCtor = context.$options._base
  // plain options object: turn it into a constructor
  if (isObject(Ctor)) {
    Ctor = baseCtor.extend(Ctor as typeof Component)
  }
  // if at this stage it's not a constructor or an async component factory,
  // reject.
  if (typeof Ctor !== 'function') {
    if (__DEV__) {
      warn(`Invalid Component definition: ${String(Ctor)}`, context)
    }
    return
  }
  // async component
  let asyncFactory
  // @ts-expect-error
  if (isUndef(Ctor.cid)) {
    asyncFactory = Ctor
    Ctor = resolveAsyncComponent(asyncFactory, baseCtor)
    if (Ctor === undefined) {
      // return a placeholder node for async component, which is rendered
      // as a comment node but preserves all the raw information for the node.
      // the information will be used for async server-rendering and hydration.
      return createAsyncPlaceholder(asyncFactory, data, context, children, tag)
    }
  }
  data = data || {}
  // resolve constructor options in case global mixins are applied after
  // component constructor creation
  resolveConstructorOptions(Ctor as typeof Component)
  // transform component v-model data into props & events
  if (isDef(data.model)) {
    // @ts-expect-error
    transformModel(Ctor.options, data)
  }
  // extract props
  // @ts-expect-error
  const propsData = extractPropsFromVNodeData(data, Ctor, tag)
  // functional component
  // @ts-expect-error
  if (isTrue(Ctor.options.functional)) {
    return createFunctionalComponent(
      Ctor as typeof Component,
      propsData,
      data,
      context,
      children
    )
  }
  // extract listeners, since these needs to be treated as
  // child component listeners instead of DOM listeners
  const listeners = data.on
  // replace with listeners with .native modifier
  // so it gets processed during parent component patch.
  data.on = data.nativeOn
  // @ts-expect-error
  if (isTrue(Ctor.options.abstract)) {
    // abstract components do not keep anything
    // other than props & listeners & slot
    // work around flow
    const slot = data.slot
    data = {}
    if (slot) {
      data.slot = slot
    }
  }
  // install component management hooks onto the placeholder node
  installComponentHooks(data)
  // return a placeholder vnode
  // @ts-expect-error
  const name = getComponentName(Ctor.options) || tag
  const vnode = new VNode(
    // @ts-expect-error
    `vue-component-${Ctor.cid}${name ? `-${name}` : ''}`,
    data,
    undefined,
    undefined,
    undefined,
    context,
    // @ts-expect-error
    { Ctor, propsData, listeners, tag, children },
    asyncFactory
  )
  return vnode
}如果是第一次执行 resolveAsyncComponent,除非使用高级异步组件 0 delay 去创建了一个 loading 组件,否则返回是 undefined,接着通过 createAsyncPlaceholder 创建一个注释节点作为占位符,同时把 asyncFactory 和 asyncMeta 赋值给当前 vnode 。
createAsyncPlaceholder 方法
export function createAsyncPlaceholder(
  factory: Function,
  data: VNodeData | undefined,
  context: Component,
  children: Array<VNode> | undefined,
  tag?: string
): VNode {
  const node = createEmptyVNode()
  node.asyncFactory = factory
  node.asyncMeta = { data, context, children, tag }
  return node
}当执行 forceRender 的时候,会触发组件的重新渲染,会再一次执行 resolveAsyncComponent。此时,会根据不同的情况,可能返回 loading、error 或成功加载的异步组件,返回值不为 undefined,因此会走正常的组件 render、patch 过程,与组件第一次渲染流程不一样,这个时候是存在新旧 vnode 的,会进行组件更新的 patch 过程。
