Vue3.0源码学习——初始化流程分析(2.挂载过程)

363 阅读3分钟

「这是我参与2022首次更文挑战的第2天,活动详情查看:2022首次更文挑战」。

前言

前文 Vue3.0源码学习——初始化流程分析(1.实例创建过程) 了解了Vue在创建实例后返回了一个对象,这个对象里有一个挂载函数 mount,这篇文章就来学习一下Vue挂载的过程

查看挂载函数的调用栈

  • createAppAPI 函数中找到 mount 的定义,位置在 /packages/runtime-core/src/apiCreateApp.ts,这就是初始化 createApp().mount('#app') 调用的挂载函数,一开始还未挂载,因此 isMounted === false 在这里打上一个断点

图片.png

  • 看到在两处调用了 mount 函数

图片.png

  • 第一步在 todomvc 中初始化调用了

图片.png

  • 第二步在 createApp (/packages/runtime-dom/src/index.ts) 中 实例app 上重新定义了 mount 方法,其中执行了原始的 mount 方法,目的是为了扩展,兼容浏览器环境,这里不做展开将在后序篇章中进行学习 图片.png

  • 通过查看调用栈,能够比较清晰和快速的了解原始 mount 函数在初始化过程中在哪些地方进行了调用

在源码中查看挂载函数的执行流程

  • 首先第一次执行 mount 组件还未挂载,isMounted === falseif 分支
      mount(
        : HostElement,
        isHydrate?: boolean,
        isSVG?: boolean
      ): any {
        if (!isMounted) { // 走这里
          // 1. 创建根组件的vnode,将真实dom转为虚拟dom
          const vnode = createVNode(
             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 {
            // 2. 将虚拟dom渲染转为真实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__) {
            app._instance = vnode.component
            devtoolsInitApp(app, version)
          }

          return getExposeProxy(vnode.component!) || vnode.component!.proxy
        }
        ...
    1. 创建根组件的vnode,将真实dom转为虚拟dom,
const vnode = createVNode(
    rootComponent as ConcreteComponent,
    rootProps
)

为什么要用虚拟dom:创建真实DOM的代价高,真实的 dom 节点实现的属性很多,而 vnode 仅仅实现一些必要的属性,相比起来,创建一个 vnode 的成本比较低。

    1. 将虚拟dom渲染转为真实dom
render(vnode, rootContainer, isSVG)
  • 这个 render 函数在上一章介绍过,在Vue3中最大的渲染函数 baseCreateRenderer 中定义,并在执行createAppAPI 时作为第一个参数传入,位置 packages\runtime-core\src\renderer.ts
// 创建渲染函数
function baseCreateRenderer(
  options: RendererOptions,
  createHydrationFns?: typeof createHydrationFunctions
): any {

  ...
  
  const render: RootRenderFunction = (vnode, container, isSVG) => {
    if (vnode == null) {
      if (container._vnode) {
        unmount(container._vnode, null, null, true)
      }
    } else {
      // 首次patch第一个参数为null
      // 首次patch是挂载,不更新
      // 此处vnode会被patch转化为真实dom对象,并追加到container容器中
      patch(container._vnode || null, vnode, container, null, null, null, isSVG)
    }
    flushPostFlushCbs()
    container._vnode = vnode
  }
  
  ...
  
  return {
    render,
    hydrate,
    createApp: createAppAPI(render, hydrate)
  }
} 
  • patch 函数的执行将在下一篇文章中进行学习

小结

挂载的流程就是生成 vnode 传递给 patch 函数转换为真实dom并挂载在宿主元素上

往期回顾 Vue3.0源码学习——初始化流程分析(1.实例创建过程)

Vue3.0源码学习——整体架构

Vue3.0源码学习——环境搭建