Vue3源码之初始化渲染流程解读二

472 阅读6分钟

在上一篇文章# Vue3源码之初始化渲染流程解读一中,主要分析了createApp()方法的具体实现。

本片文章接上一篇文章,接着分析初始化渲染mount()部分

1. mount方法定义

我们在createApp().mount()中调用的mount()方法是在导出createApp()方法内部定义的

// packages\runtime-dom\src\index.ts
/**
* @param {args} 根组件实例
*/ 
export const createApp = ((...args) => {
  const app = ensureRenderer().createApp(...args)
  // 保存app实例mount方法
  const { mount } = app
  // 重写实例的mount方法;
  app.mount = (containerOrSelector: Element | ShadowRoot | string): any => {
    // 调用normalizeContainer获取根元素容器
    const container = normalizeContainer(containerOrSelector)
    if (!container) return
    // 获取根组件实例对象,就是我们在createApp()方法里面传入的根应用对象
    const component = app._component
    // 渲染优先级:render > template > container.innerHTML
    if (!isFunction(component) && !component.render && !component.template) {
      component.template = container.innerHTML
    }
    // 挂载前清空挂载容器内部内容
    container.innerHTML = ''
    // 执行挂载
    const proxy = mount(container, false, container instanceof SVGElement)
    if (container instanceof Element) {
      container.removeAttribute('v-cloak')
      // 挂载容器添加app属性标识
      container.setAttribute('data-v-app', '')
    }
    // 返回根组件实例
    return proxy
  }
  // 返回应用实例以支持链式调用
  return app
})

流程可以简单总结为:

  • 根据mount(container)传入参数container获取挂载容器
  • 获取createApp(args)传入参数args根应用对象
  • 依次判断获取render > template > container.innerHTML作为渲染内容
  • 挂载前清空目标容器
  • 调用app应用属性方法mount挂载并添加根应用属性标识data-v-app

2. normalizeContainer 获取目标挂载容器

// packages\runtime-dom\src\index.ts
function normalizeContainer(
  container: Element | ShadowRoot | string
): Element | null {
  if (isString(container)) {
    const res = document.querySelector(container)
    if (__DEV__ && !res) {
      warn(
        `Failed to mount app: mount target selector "${container}" returned null.`
      )
    }
    return res
  }
  return container as any
}

3. 被重写mount方法

// packages\runtime-core\src\apiCreateApp.ts
export function createAppAPI<HostElement>(
  render: RootRenderFunction,
  hydrate?: RootHydrateFunction
): CreateAppFunction<HostElement> {
  return function createApp(rootComponent, rootProps = null) {
    if (rootProps != null && !isObject(rootProps)) {
      __DEV__ && warn(`root props passed to app.mount() must be an object.`)
      rootProps = null
    }
    // appContext对象
    const context = createAppContext()
    // 保存当前注册plugin
    const installedPlugins = new Set()
    // 当前未挂载标识
    let isMounted = false
    /***
     *
     * 1. context.app = {...}
     * 2. const app: App = context
     */
    const app: App = (context.app = {
      _uid: uid++,
      _component: rootComponent as ConcreteComponent,
      _props: rootProps,
      _container: null,
      _context: context,

      version,

      get config() {
        return context.config
      },

      set config(v) {
        if (__DEV__) {
          warn(
            `app.config cannot be replaced. Modify individual options instead.`
          )
        }
      },
      // 省略 use,mixin, component,directive,unmount,provide
      mount(
        rootContainer: HostElement,
        isHydrate?: boolean,
        isSVG?: boolean
      ): any {
        if (!isMounted) {
          // 获取vnode
          const vnode = createVNode(
            rootComponent as ConcreteComponent,
            rootProps
          )
          // store app context on the root VNode.
          // this will be set on the root instance on initial mount.
          vnode.appContext = context

          // HMR root reload
          if (__DEV__) {
            context.reload = () => {
              render(cloneVNode(vnode), rootContainer, isSVG)
            }
          }

          if (isHydrate && hydrate) {
            hydrate(vnode as VNode<Node, Element>, rootContainer as any)
          } else {
            // vnode渲染真实dom
            render(vnode, rootContainer, isSVG)
          }
          // 挂载标志位
          isMounted = true
          app._container = rootContainer
          // for devtools and telemetry
          ;(rootContainer as any).__vue_app__ = app

          if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
            devtoolsInitApp(app, version)
          }

          return vnode.component!.proxy
        } else if (__DEV__) {
          warn(
            `App has already been mounted.\n` +
              `If you want to remount the same app, move your app creation logic ` +
              `into a factory function and create fresh app instances for each ` +
              `mount - e.g. \`const createMyApp = () => createApp(App)\``
          )
        }
      },
    // 返回app实例
    return app
  }
}

mount方法内部流程总结:

  • 首先根据全局挂载标识isMounted判断应用是否已经挂载,已挂载不做任何处理
  • 调用createVNode方法,根据createApp(args)传入参数args创建vnode
  • vnode增加appContext应用上下文变量
  • 依据vnode调用render方法,虚拟dom渲染真是dom
  • 修改已挂载标识
  • 应用实例保存挂载容器

4. createVNode

createVnode主要用来创建vnode

vnode的本质是一个用来描述真是dom的javascript对象;vue3中通过shapeFlag对vnode进行了更详细的区分,以便在patch diff的过程中进行更精准的处理操作。

注意:有一点需要我们理解,即组件vnode其实是对抽象功能的描述,并不会在页面渲染一个真实的dom标签,真正渲染的是组件内部的html标签。

// packages\runtime-core\src\vnode.ts
function _createVNode(
  type: VNodeTypes | ClassComponent | typeof NULL_DYNAMIC_COMPONENT, // 节点类型
  props: (Data & VNodeProps) | null = null, // class style event 组件props
  children: unknown = null,
  patchFlag: number = 0, //
  dynamicProps: string[] | null = null,
  isBlockNode = false
): VNode {
  

  if (isVNode(type)) {
    // 本身是vnode,直接复制
    const cloned = cloneVNode(type, props, true /* mergeRef: true */)
    if (children) {
      normalizeChildren(cloned, children)
    }
    return cloned
  }

  // 处理class style props
  if (props) {
    // for reactive or proxy objects, we need to clone it to enable mutation.
    if (isProxy(props) || InternalObjectKey in props) {
      props = extend({}, props)
    }
    let { class: klass, style } = props
    if (klass && !isString(klass)) {
      props.class = normalizeClass(klass)
    }
    if (isObject(style)) {
      // reactive state objects need to be cloned since they are likely to be
      // mutated
      if (isProxy(style) && !isArray(style)) {
        style = extend({}, style)
      }
      props.style = normalizeStyle(style)
    }
  }

  // 对vnode进行编码,区分vnode类型
  const shapeFlag = isString(type)
    ? ShapeFlags.ELEMENT
    : __FEATURE_SUSPENSE__ && isSuspense(type)
      ? ShapeFlags.SUSPENSE
      : isTeleport(type)
        ? ShapeFlags.TELEPORT
        : isObject(type)
          ? ShapeFlags.STATEFUL_COMPONENT
          : isFunction(type)
            ? ShapeFlags.FUNCTIONAL_COMPONENT
            : 0
  // 定义vnode描述对象
  const vnode: VNode = {
    __v_isVNode: true, // 判断vnode标识
    __v_skip: true,
    type,
    props,
    key: props && normalizeKey(props),
    ref: props && normalizeRef(props),
    scopeId: currentScopeId,
    slotScopeIds: null,
    children: null,
    component: null,
    suspense: null,
    ssContent: null,
    ssFallback: null,
    dirs: null,
    transition: null,
    el: null,
    anchor: null,
    target: null,
    targetAnchor: null,
    staticCount: 0,
    shapeFlag,
    patchFlag,
    dynamicProps,
    dynamicChildren: null,
    appContext: null
  }
  // 标准化子节点,将子节点处理为数组或文本节点
  normalizeChildren(vnode, children)
  // 
  return vnode
}

通过createVnode内部实现我们可以看到,创建vnode主要有:

  • 首先根据type属性判断是否为vnode,是vnode则直接复制
  • 处理原生属性class style
  • 对vnode shapeFlag进行信息编码
  • 定义vnode描述对象
  • 标准化子节点

5. render

在上面createVnode方法获取到vnode之后,通过调用render执行虚拟dom渲染真实dom; render方法是createAppAPI方法的参数

export function createAppAPI<HostElement>(
  render: RootRenderFunction,
  hydrate?: RootHydrateFunction
): CreateAppFunction<HostElement> {
  return function createApp(rootComponent, rootProps = null) {
      const app: App = (context.app = {
          mount() {
              ...
          }
      }
  }
}

render方法真正的定义在文件:packages\runtime-core\src\renderer.ts

  const patch: PatchFn = (
    n1,
    n2,
    container,
    anchor = null,
    parentComponent = null,
    parentSuspense = null,
    isSVG = false,
    slotScopeIds = null,
    optimized = false
  ) => {
      const render: RootRenderFunction = (vnode, container, isSVG) => {
        // vnode 新vdom
        // container._vnode 旧vdom
        if (vnode == null) {
          if (container._vnode) {
            // 新vnode为空,存在旧vnode情况下,卸载
            unmount(container._vnode, null, null, true)
          }
        } else {
          // 创建or更新
          patch(container._vnode || null, vnode, container, null, null, null, isSVG)
        }
        flushPostFlushCbs()
        // 保存vnode
        container._vnode = vnode
      }
      
      return {
        render,
        hydrate,
        createApp: createAppAPI(render, hydrate)
      }
  }

render渲染函数内部流程说明:

  • container._vnode为保存上一份 vnode
  • 如果传入新vnode不存在而存在旧vnode,则执行卸载
  • 根据新传入vnode和获取保留旧vnode执行新创建或者更新,初次渲染container._vnode为null,执行新建
  • 更新保存container._vnode

6.patch

patch是vue3源码部分非常核心和重要的一个方法,初始化渲染以及更新diff相关全部在此方法内部定义执行,代码量也非常庞大。

// packages\runtime-core\src\renderer.ts
const patch: PatchFn = (
    n1, // old vnode
    n2, // new vnode
    container, // 挂载容器
    anchor = null, // 是一个锚点,用来标识当我们对新旧节点做增删或移动等操作时,以哪个节点为参照物
    parentComponent = null, // 父组件
    parentSuspense = null,
    isSVG = false, // 是否svg标识
    slotScopeIds = null,
    optimized = false // 是否开启优化
  ) => {
    // 旧节点存在并且与新节点类型不一致,卸载旧节点
    if (n1 && !isSameVNodeType(n1, n2)) {
      anchor = getNextHostNode(n1)
      unmount(n1, parentComponent, parentSuspense, true)
      n1 = null
    }
    // PatchFlag == -2,则跳出优化模式
    if (n2.patchFlag === PatchFlags.BAIL) {
      optimized = false
      n2.dynamicChildren = null
    }
    // 获取新vnode对象type,ref, shapeFlag
    const { type, ref, shapeFlag } = n2;
    // 根据 Vnode 类型判断
    switch (type) {
      // 文本类型
      case Text:
        processText(n1, n2, container, anchor)
        break
      // 注释
      case Comment:
        processCommentNode(n1, n2, container, anchor)
        break
      // 静态节点类型
      case Static:
        if (n1 == null) {
          mountStaticNode(n2, container, anchor, isSVG)
        } else if (__DEV__) {
          patchStaticNode(n1, n2, container, isSVG)
        }
        break
      // Fragment 类型
      case Fragment:
        processFragment(
          n1,
          n2,
          container,
          anchor,
          parentComponent,
          parentSuspense,
          isSVG,
          slotScopeIds,
          optimized
        )
        break
      // 元素类型、组件类型、teleport、supense
      default:
        if (shapeFlag & ShapeFlags.ELEMENT) {
          // 按位与运算,判断是一个元素类型
          processElement(
            n1,
            n2,
            container,
            anchor,
            parentComponent,
            parentSuspense,
            isSVG,
            slotScopeIds,
            optimized
          )
        } else if (shapeFlag & ShapeFlags.COMPONENT) {
          // 按位与运算,判断是一个组件类型
          processComponent(
            n1,
            n2,
            container,
            anchor,
            parentComponent,
            parentSuspense,
            isSVG,
            slotScopeIds,
            optimized
          )
        } else if (shapeFlag & ShapeFlags.TELEPORT) {
          ;(type as typeof TeleportImpl).process(
            n1 as TeleportVNode,
            n2 as TeleportVNode,
            container,
            anchor,
            parentComponent,
            parentSuspense,
            isSVG,
            slotScopeIds,
            optimized,
            internals
          )
        } else if (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) {
          ;(type as typeof SuspenseImpl).process(
            n1,
            n2,
            container,
            anchor,
            parentComponent,
            parentSuspense,
            isSVG,
            slotScopeIds,
            optimized,
            internals
          )
        } else if (__DEV__) {
          warn('Invalid VNode type:', type, `(${typeof type})`)
        }
    }
  }

7. processComponent

在初始化挂载阶段,我们通过createApp(App).mount('#app')在createApp传入的参数App为一个component类型,所以在第一次进入patch阶段,通过vnode.type和shapeFlag判断,会进入processComponent处理组件方法。

const processComponent = (
    n1: VNode | null, // old vnode
    n2: VNode, // new vnode
    container: RendererElement, // 挂载容器
    anchor: RendererNode | null,
    parentComponent: ComponentInternalInstance | null,
    parentSuspense: SuspenseBoundary | null,
    isSVG: boolean,
    slotScopeIds: string[] | null,
    optimized: boolean
  ) => {
    n2.slotScopeIds = slotScopeIds
    if (n1 == null) {
      // keep-live
      if (n2.shapeFlag & ShapeFlags.COMPONENT_KEPT_ALIVE) {
        ;(parentComponent!.ctx as KeepAliveContext).activate(
          n2,
          container,
          anchor,
          isSVG,
          optimized
        )
      } else {
        // 初始化挂载,n1为null, 会执行此处,挂载组件方法
        mountComponent(
          n2,
          container,
          anchor,
          parentComponent,
          parentSuspense,
          isSVG,
          optimized
        )
      }
    } else {
      // 更新组件
      updateComponent(n1, n2, optimized)
    }
  }

8. mountComponent

const mountComponent: MountComponentFn = (
    initialVNode,
    container,
    anchor,
    parentComponent,
    parentSuspense,
    isSVG,
    optimized
  ) => {
    // 创建组件实例
    const compatMountInstance = __COMPAT__ && initialVNode.component
    const instance: ComponentInternalInstance =
      compatMountInstance ||
      (initialVNode.component = createComponentInstance(
        initialVNode,
        parentComponent,
        parentSuspense
      ))

    // resolve props and slots for setup context
    if (!(__COMPAT__ && compatMountInstance)) {
      if (__DEV__) {
        startMeasure(instance, `init`)
      }
      // 设置组件实例
      setupComponent(instance)
      if (__DEV__) {
        endMeasure(instance, `init`)
      }
    }
    // 设置并运行副作用函数
    setupRenderEffect(
      instance,
      initialVNode,
      container,
      anchor,
      parentSuspense,
      isSVG,
      optimized
    )

    if (__DEV__) {
      popWarningContext()
      endMeasure(instance, `mount`)
    }
  }

9. setupComponent

// packages\runtime-core\src\component.ts
export function setupComponent(
  instance: ComponentInternalInstance, // 组件实例
  isSSR = false
) {
  isInSSRComponentSetup = isSSR
  // 获取props children
  const { props, children } = instance.vnode
  // 
  const isStateful = isStatefulComponent(instance)
  // 初始化props
  initProps(instance, props, isStateful, isSSR)
  // 初始化slot
  initSlots(instance, children)
  // 开始解析执行setup
  const setupResult = isStateful
    ? setupStatefulComponent(instance, isSSR)
    : undefined
  isInSSRComponentSetup = false
  return setupResult
}

10. setupStatefulComponent

// packages\runtime-core\src\component.ts

function setupStatefulComponent(
  instance: ComponentInternalInstance,
  isSSR: boolean
) {
  const Component = instance.type as ComponentOptions

  
  // 实例添加缓存对象
  instance.accessCache = Object.create(null)
  // 实例全局代理
  instance.proxy = new Proxy(instance.ctx, PublicInstanceProxyHandlers)
  
  // 解析获取setup函数,执行setup并获取return返回值
  const { setup } = Component
  if (setup) {
    // setup.length获取setup方法内部参数,》1即创建传入ctx上下文对象
    const setupContext = (instance.setupContext =
      setup.length > 1 ? createSetupContext(instance) : null)

    currentInstance = instance
    pauseTracking()
    // 执行setup并拿到返回值
    const setupResult = callWithErrorHandling(
      setup,
      instance,
      ErrorCodes.SETUP_FUNCTION,
      [__DEV__ ? shallowReadonly(instance.props) : instance.props, setupContext]
    )
    resetTracking()
    currentInstance = null
    // 如果返回promise
    if (isPromise(setupResult)) {
      if (isSSR) {
        // return the promise so server-renderer can wait on it
        return setupResult
          .then((resolvedResult: unknown) => {
            handleSetupResult(instance, resolvedResult, isSSR)
          })
          .catch(e => {
            handleError(e, instance, ErrorCodes.SETUP_FUNCTION)
          })
      } else if (__FEATURE_SUSPENSE__) {
        // async setup returned Promise.
        // bail here and wait for re-entry.
        instance.asyncDep = setupResult
      } else if (__DEV__) {
        warn(
          `setup() returned a Promise, but the version of Vue you are using ` +
            `does not support it yet.`
        )
      }
    } else {
      // 处理返回结果
      handleSetupResult(instance, setupResult, isSSR)
    }
  } else {
    finishComponentSetup(instance, isSSR)
  }
}

11. finishComponentSetup

vue3兼容vue2 options Api,在执行完setup之后,开始兼容处理options API,这里我们就可以看出来,setup方法的执行非常靠前。

export function finishComponentSetup(
  instance: ComponentInternalInstance,
  isSSR: boolean,
  skipOptions?: boolean
) {
  const Component = instance.type as ComponentOptions
  // 实例没有render函数,即setup没有返回render函数
  if (!instance.render) {
    // 检查是否在options api有注册render
    if (compile && !Component.render) {
      // 没有render则获取template
      const template =
        (__COMPAT__ &&
          instance.vnode.props &&
          instance.vnode.props['inline-template']) ||
        Component.template
      if (template) {
        ...
        ...
        // 执行compile编译template,并将结果保存组件render
        Component.render = compile(template, finalCompilerOptions)
        if (__DEV__) {
          endMeasure(instance, `compile`)
        }
      }
    }
    // 实例挂载render
    instance.render = (Component.render || NOOP) as InternalRenderFunction
    ...
    ...
}

12. setupRenderEffect

主要作用给实例添加update方法

const setupRenderEffect: SetupRenderEffectFn = (
    instance,
    initialVNode,
    container,
    anchor,
    parentSuspense,
    isSVG,
    optimized
  ) => {
      // 创建响应式副作用渲染函数
      instance.update = effect(function componentEffect() {
          if (!instance.isMounted) {
              // 挂载
              // beforeMount hook
              instance.emit('hook:beforeMount')、
              // 渲染组件生成子树vnode
              const subTree = (instance.subTree = renderComponentRoot(instance))
              // 子树vnode 挂载 container
              patch(
                null,
                subTree,
                container,
                anchor,
                instance,
                parentSuspense,
                isSVG
              )
              // mounted hook
              instance.emit('hook:mounted')
              // 修改挂载标志位
              instance.isMounted = true
              // 保留渲染生成的子树根 DOM 节点
              initialVNode = container = anchor = null as any
          } else {
              // 更新
          }
      })
  }

13. renderComponentRoot

方法主要执行instance实例上render方法,获取vnode;我们知道每个组件都有对应的render函数,即使我们在日常开发中使用的template模板,也都会经过compile编译返回render函数。

// packages\runtime-core\src\componentRenderUtils.ts
function renderComponentRoot(
  instance: ComponentInternalInstance
): VNode {
    const {
        type: Component,
        vnode,
        proxy,
        withProxy,
        props,
        propsOptions: [propsOptions],
        slots,
        attrs,
        emit,
        render,
        renderCache,
        data,
        setupState,
        ctx
      } = instance
      let result
      try {
    let fallthroughAttrs
    if (vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT) {
      // 标准化vnode
      result = normalizeVNode(
        // 执行render
        render!.call(
          proxyToUse,
          proxyToUse!,
          renderCache,
          props,
          setupState,
          data,
          ctx
        )
      )
      fallthroughAttrs = attrs
    }
}

14. processElement

在获取到subTree子组件vnode之后,会再次进入patch方法,将子vnode挂载到父container容器,这就是一个递归渲染子组件的过程。

在递归渲染子组件过程中,遇到真实html标签,表明这是一个真是的dom元素,会在patch方法内部,根据vnode type判断执行processElement处理标签元素

const processElement = (
    n1: VNode | null,
    n2: VNode,
    container: RendererElement,
    anchor: RendererNode | null,
    parentComponent: ComponentInternalInstance | null,
    parentSuspense: SuspenseBoundary | null,
    isSVG: boolean,
    slotScopeIds: string[] | null,
    optimized: boolean
  ) => {
    isSVG = isSVG || (n2.type as string) === 'svg'
    if (n1 == null) {
      // 旧节点不存在,否则直接挂载新节点
      mountElement(
        n2,
        container,
        anchor,
        parentComponent,
        parentSuspense,
        isSVG,
        slotScopeIds,
        optimized
      )
    } else {
      // 如果存在旧节点,则继续通过 patch 比较新旧两个节点
      patchElement(
        n1,
        n2,
        parentComponent,
        parentSuspense,
        isSVG,
        slotScopeIds,
        optimized
      )
    }
  }

15. mountElement

在processElement内部,因为是首次渲染,n1为null,所以会到mountElement方法,挂载元素节点

const mountElement = (
    vnode: VNode,
    container: RendererElement,
    anchor: RendererNode | null,
    parentComponent: ComponentInternalInstance | null,
    parentSuspense: SuspenseBoundary | null,
    isSVG: boolean,
    slotScopeIds: string[] | null,
    optimized: boolean
  ) => {
    let el: RendererElement
    let vnodeHook: VNodeHook | undefined | null
    const { type, props, shapeFlag, transition, patchFlag, dirs } = vnode
    if (
      !__DEV__ &&
      vnode.el &&
      hostCloneNode !== undefined &&
      patchFlag === PatchFlags.HOISTED
    ) {
      // 复制dom元素节点
      el = vnode.el = hostCloneNode(vnode.el)
    } else {
      // 创建dom元素节点
      el = vnode.el = hostCreateElement(
        vnode.type as string,
        isSVG,
        props && props.is,
        props
      )
      
      if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {
        // 处理纯文本子节点
        hostSetElementText(el, vnode.children as string)
      } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
        // 子节点是数组,即子节点包含多个元素
        mountChildren(
          vnode.children as VNodeArrayChildren,
          el,
          null,
          parentComponent,
          parentSuspense,
          isSVG && type !== 'foreignObject',
          slotScopeIds,
          optimized || !!vnode.dynamicChildren
        )
      }
      
      if (dirs) {
        invokeDirectiveHook(vnode, null, parentComponent, 'created')
      }
      // 处理props class style event
      if (props) {
        for (const key in props) {
          // 如果prop 不是key ref,则patch prop
          if (!isReservedProp(key)) {
            hostPatchProp(
              el,
              key,
              null,
              props[key],
              isSVG,
              vnode.children as VNode[],
              parentComponent,
              parentSuspense,
              unmountChildren
            )
          }
        }
        if ((vnodeHook = props.onVnodeBeforeMount)) {
          // 调用声明周期函数BeforeMount
          invokeVNodeHook(vnodeHook, parentComponent, vnode)
        }
      }
      // scopeId
      setScopeId(el, vnode, vnode.scopeId, slotScopeIds, parentComponent)
    }
    // 挂载el到container容器
    hostInsert(el, container, anchor)
  }

可以看到,挂载元素函数主要做四件事:创建 DOM 元素节点、处理 props、处理 children、挂载 DOM 元素到 container 上。

16. mountChildren

  const mountChildren: MountChildrenFn = (
    children,
    container,
    anchor,
    parentComponent,
    parentSuspense,
    isSVG,
    slotScopeIds,
    optimized,
    start = 0
  ) => {
    // 遍历children
    for (let i = start; i < children.length; i++) {
      const child = (children[i] = optimized
        ? cloneIfMounted(children[i] as VNode)
        : normalizeVNode(children[i]))
      patch(
        null,
        child,
        container,
        anchor,
        parentComponent,
        parentSuspense,
        isSVG,
        slotScopeIds,
        optimized
      )
    }
  }

mountChildren方法内部比较简单,遍历每一个child,获取vnode并执行下一层patch.

17. 总结

至此,初始化渲染流程基本结束。在最后大概总结下初始化渲染流程:

  1. 根据mount方法内部传入参数获取到根应用挂载容器。
  2. 根据createApp方法传入根组件参数开始,递归构建vnode、渲染vnode、挂载。
  3. 组件的挂载顺序是由里到外,最开始解析的后挂载。