Vue3 VNode 到 DOM如何转换?

311 阅读3分钟

这个问题还能怎么理解?

关键词:初始化、挂载、DOM、VNode、createApp、创建实例

  1. Vue实例挂载的时候都发生了什么?
  2. Vue如何创建实例的?
  3. Vue createApp都发生了什么?
  4. Vue是怎么将组件渲染成DOM的?
  5. Vue初始化的时候都做了什么?

一. 着重了解什么?

  1. 延时创建渲染器
    1. 便于 tree-shaking 去除渲染相关的核心代码
  2. 重写 app.mount 方法

二. vnode渲染需要解决什么问题?

image.png

三. 延时创建渲染器

只有在 createApp() 时才才会去通过 ensureRenderer 方法去创建渲染器。所谓渲染器就是 app.mount() 相关的逻辑。好处是可以在只使用vue响应式内容时,自动将渲染相关逻辑进行tree shaking。

const app = ensureRenderer().createApp(...args)

所谓渲染器,就是 ensureRenderer().createApp(...args) 返回复写前的app.mount,它只有 模板 -> vnode -> 渲染vnode 逻辑。

四. 重写 app.mount 方法

因为不同平台的代码mount逻辑不同,这里需要根据不同平台作区分,比如:weex、小程序和web就不一样。但无论什么平台,本质都是 模板 -> vnode -> 渲染vnode。重写只是在渲染器外添加区分平台,拿到container和template的逻辑。

重写的内容补充了什么?

image.png

渲染器是 createApp() 创建根实例时用到的,所以渲染器的第一步是创建根实例vnode。

五. 创建根组件vnode

其实就是根据组件类型,拼出 vnode 对象。最后序列化一下子节点。把子节点整理成数组的形式。

image.png

六. 根节点vnode渲染成dom

这一步主要就是通过 render 渲染器实现,本质就是对根节点子树vnode调用patch。

image.png

七. Patch 将组件的vnode渲染成dom

Patch 的功能就是将 vnode 渲染成dom。同时兼备首次渲染,新老vnode更新,组件卸载的能力。会根据vnode的类型分情况处理。

如果新老vnode type不同,则直接销毁原有。

image.png

vue是如何判断vnode是相同类型的?

只是判断新老 vnode.type 和 vnode.key 是否都严格相等。

function isSameVNodeType (n1, n2) {
  // n1 和 n2 节点的 type 和 key 都相同,才是相同节点
  return n1.type === n2.type && n1.key === n2.key
}

八. processComponent 创建组件

processComponent 即负责首次挂载组件么,也负责组件更新。

因为组件vnode本质上只是一个占位符,需要创建这个组件实例,然后挂载这个组件的dom。创建组件的过程需要相比挂载普通dom多出 创建组件实例、设置组件实例、创建组件的副作用渲染函数。

  • 创建组件实例
  • 设置组件实例:执行setup函数,处理props、插槽...
  • 创建组件的副作用渲染函数(和 vnode -> dom 渲染相关的就在这儿)

这个副作用渲染函数负责组建的首次挂载过程,也负责组件更新的过程,他就是被响应式变量更新时派发的render effect,下面是render effect的核心流程:

image.png

九. processElement 创建普通DOM元素

processElement 即负责首次渲染DOM,也负责更新dom,diff算法的逻辑就在更新dom这里。

根据 vnode 创建 dom 元素的过程就是简单的一系列原生操作。

如果有子节点,就会 mountChildren 去用 patch 方法挂载子节点。

所以说,节点创建和挂载的过程是一个深度优先遍历的过程。父先创建,子后创建,子先挂载,父后挂载。mounted生命周期即使如此。

image.png

补充:mountElement会先通过document.createElement创建当前节点,然后递归子节点去patch,等子节点都patch完成,最外层就会插入到container上。