Vue3.2 DOM初始化挂载流程

851 阅读7分钟

8488abf37bf946809df3e07a0532e1fe.jpg

前言

本篇文章是分析Vue3.2 DOM初始化挂载流程,分析在vue初始化流程之后的第一次挂载,如果想要了解初始化流程可以先去看一下vue3.2 初始化的时候做了什么?这篇文章。

在挂载的流程中,可能会遇到组件,vue把这些东西统一归为 "资产",指令、组件还有vue2的过滤器都被认为是资产,在本文中只会讲到组件,其他的后面的文章会一步步分析。

DOM初始化挂载

image.png

继上次vue初始化流程,到最后会执行componentUpdateFn函数,在这个函数开头执行了三个beforeMount,分别是v3的beforeMount生命周期选项、onVnodeBeforeMount生命周期函数、v2的hook:beforeMount事件函数。但是这些都不是重点。

image.png

if分支是服务端渲染,我们这里是客户端渲染,所有走else分支,在第1533行通过template编译后的render渲染函数产生了最初的vnode,交给了patch函数去解析,在初始化流程中,一定是没有旧的vnode。走的都是挂载。

image.png

这里先提前说一下这些参数分别是什么,n1是旧的vnoden2是新的vnodecontainer是当前vnode宿主元素、anchor是挂载元素时的瞄点、parentComponent是当前vnode父组件,没有就是nullparentSuspense是包着当前vnode最近的内置组件<Suspense>,没有就是nullisSVG:当前节点是SVG吗、slotScopeIds是插槽作用id,optimeized是优化模式还是非优化模式,

image.png

只有一个根元素

前面三步,在初始化流程中绝对不会执行的,App根组件已经解析过了,现在解析应该是第一个节点,看下面的这个结构。

const App = {
   template: `
    <div id="app">
        <div>{{msg}}</div>
    </div>
   `,
   
   setup() {
       const msg = ref('vue3初始化挂载')
       return {
           msg
       }
   }
}

最外层的idappdiv是宿主元素,解析的是他的第一个子元素,在上面的案例中,第一个是一个div,走解析元素的分支,执行processElement

image.png

image.png

没有旧节点,走mountElement分支,在里面会根据节点类型创建一个新元素。

image.png

image.png

这里会有两种情况,先说第一种比较简单,当前节点的子节点只是一个字符串,插入就可以,接着往后执行,后的就是和有关指令、props之类,我们没有使用这里也就不关心,再往后就是将元素插入到容器中,这里的容器是宿主元素app,执行完这个页面中就可以看到了,最后回到patch函数中,patch是直接结束了,整个流程结束了。

image.png

如果这个节点有多个子节点,就会走mountChild分支,遍历每一个子节点再次调用patch。所以总体流程如下:

7dce2bbee00f143ca03c0291d17a099.png

有多个根元素

v2不允许用户在一个模板写多个根元素,只允许有一个,v3允许有多个根元素,看下面的这个例子:

const App = {
    template: `
        <div>{{counter}}</div>
        <button @click="addCounter"></button>
    `, 
    setup() {/* code... */}
}

image.png

image.png

如果有多个同级别的节点,且不把它们包在同一个节点中,就会产生一个片段(Fragment),在patch函数中他就会走processFragment分支,解析一个片段。转换成渲染函数,会看到vue给这个代码片段加了一个抽象的父节点Fragment,这其实vue自己内部实现的。

image.png

fragmentStartAnchorfragmentEndAnchor是作为这个代码片段的开始点和结束点,在初始化时这两个东西时不存在的,会创建两个空字符串作为这两个东西。

image.png

在开始解析Fragment子元素之前,会将两个瞄点先插入到容器中,在上面的例子中会插入的宿主元素app中,下面就开始使用mountChildren解析Fragment子元素,最后还是再次调用patch函数。总体流程如下,

image.png

静态节点、文本节点、注释节点

有如下模板:

const App = {
    template: `
        <!-- vue3初始化挂载 -->
        <div id="1">
          <div id="1">
            <div id="1">
              <div id="1">
                <div id="1"></div>
              </div>
            </div>
          </div>
        </div>
        <div :class="foo"></div>
        vue3初始化挂载
    `
}
  • 注释节点

image.png

我们先来看注释节点是如何解析的,在patch函数中走的processCommentNode,在prcessCommentNode内部就是简单的创建一个注释插入的父容器中就可以了。

  • 文本节点 image.png

文本节点在patch函数中走processText,在prcessCommentNode内部就是简单的创建一个文本插入的父容器中就可以了。

  • 静态节点

image.png

一个vue树中,如果静态提升的VNode或者整个树都是静态的,vue会将其优化,进行预字符串化,而静态节点就是将字符串化之后再通过createStaticVNode创建的,在patch函数中会走mountStaticNodemountStaticNode调用insertStaticContent去追加节点到容器中

image.png

创建一个template,将字符串化的结构放进去,这个template中的content属性就会是一个fragment-document,拿出来放入父容器中。字符串结构会缓存,如果下次还是这种就可以直接使用了。

TeleportSuspense的解析流程可以去看我写的Vue3 新特性 Teleport Suspense实现原理,这篇文章详细的说明了TeleportSuspense初始化流程和更新流程。

组件的挂载

假设有如下结构:

<div>
    <Foo></Foo>
</div>

image.png

组件在解析之前会走一次定义的流程,在渲染函数中可以很清楚的看到组件交给一个函数去解析,转换成渲染函数之后交给了resolveComponent函数,但是这个函数是去调用了另外一个函数:resolveAssets函数。

image.png

函数开头,必须要存在实例,不然直接报警告然后结束,如果设置了name选项的组件有最高优先级,在校验和传递进来的name值相同可直接返回ComponentComponnet是组件配置选项。

这里有两个name,一个是options api中的选项,这个很直接设置是啥就是啥。一个是经过complie编译之后的产生的,这会和用户在模板中如何写组件标签有关,举个例子:如果是:<FooterButton></FooterButton>,就会变成 FooterButton,如果是:<footer-button></footer-button>,就会变成 footer-button,所以校验时候会进行多种字符串转换。

image.png

这里与其说是注册,其实就是去options api选项中找到对应的"资产",举个例子,这里在解析组件,需要找到组件的配置,会去当前渲染实例的components选项中找到"组件资产",如果有正常返回没有就会去全局上下文中找,还是没有那就只能返回false

image.png

或许是渲染自己,前提是没有找到"资产"和maybeSelfReferencetrue,即参照自己渲染,什么都没有就只能报错。但是如果是动态组件则一定不会参照自己,最后,如果一切正常且不参照自己,会被找到的资产返回去。

当前一切执行完毕之后,会把组件开始转换成VNode,后面流程就是渲染。

寻找资产

image.png

registery是组件实例,name就是资产的名称,是个字符串,由于名称的有多种写法,这里会进行多种转换资产名称以找到资产。

总结

vue在挂载dom的流程中,是深度优先的规则,遇到一个节点,会一直如果有子节点,会递归调用patch去解析,直到最里面的子节点解析完毕才会到下一个节点。当一个节点被解析完毕,在mountElement中执行hostInsert在页面中就可以看到这个节点存在,如果是静态节点(是元素节点的子节点)、文本节点、注释节点是直接通过histInert追加到页面中,如果只是静态节点,在执行完insertStaticContent就会在页面中看到元素。

vue的初始化流程到这里就分析完毕了,如果缺少或不对的,希望哥哥们可以指出和在评论区补充。谢谢!