Vue3 是如何渲染组件的?

为何要组件化?

渲染器主要负责将虚拟 DOM 渲染为真实 DOM,我们只需要使用虚拟 DOM 来描述最终呈现的内容即可。但当我们编写比较复杂的页面时,用来描述页面结构的虚拟 DOM 的代码量会变得越来越多,或者说页面模板会变得越来越大。这时,我们就需要组件化的能力。

有了组件,我们就可以将一个大的页面拆分为多个部分,每一个部分都可以作为单独的组件,这些组件共同组成完整的页面。从而降低了页面实现的复杂度。因为页面被拆分了,复杂度被分散到各个组件中。

组件化的实现依赖于渲染器的支持。

Vue3 中组件是什么?

从用户(Vue.js 的使用者)的角度来看,一个有状态组件就是一个选项对象,简单地说,组件其实就是对象。如下代码所示:

js 复制代码
// MyComponent 是一个组件,它的值是一个选项对象
const MyComponent = {
  name: 'MyComponent',
  data() {
    return { foo: 1 }
  }
}

从渲染器(Vue.js 内部)的内部实现来看,组件是特殊类型的虚拟 DOM 节点。渲染器使用虚拟 DOM 的 type 属性区分不同类型的元素。不同类型的元素会有不同的处理逻辑。

js 复制代码
function patch(n1, n2, container, anchor) {
  if (n1 && n1.type !== n2.type) {
    unmount(n1)
    n1 = null
  }

  const { type } = n2

  if (typeof type === 'string') {
    // 作为普通元素处理
  } else if (type === Text) {
    // 作为文本节点处理
  } else if (type === Fragment) {
    // 作为片段处理
  }
}

对于组件来说,其虚拟 DOM 的 type 属性为组件的选项对象

js 复制代码
// 该 vnode 用来描述组件,type 属性存储组件的选项对象
const vnode = {
  type: MyComponent
  // ...
}

在 patch 函数中,判断虚拟 DOM 的 type 是组件类型则会调用 mountComponent 或 patchComponent 方法来挂载或更新组件。

👆 上面的代码摘自 Vue.js 3.2.45

设计组件在用户层面的接口

对于一个框架来说,暴露给用户层面的接口是非常重要的,因为它决定了用户使用这个框架的体验。

渲染器有了处理组件的能力后,下一步要做的就是:设计组件在用户层面的接口。这包括:

  1. 用户应该如何编写组件?

  2. 组件的选项对象必须包含哪些内容?

  3. 以及组件拥有哪些能力?

  4. 其他...

实际上,组件是对页面内容的封装,它用来描述页面内容的一部分。因此,一个组件必须包含一个渲染函数,即 render 函数,并且渲染函数的返回值应该是虚拟 DOM。换句话说,组件的渲染函数就是用来描述组件所渲染内容的接口,如下面的代码所示:

js 复制代码
const MyComponent = {
  // 组件名称,可选
  name: 'MyComponent',
  // 组件的渲染函数,其返回值必须为虚拟 DOM
  render() {
    // 返回虚拟 DOM
    return {
      type: 'div',
      children: `我是文本内容`
    }
  }
}

有了基本的组件结构之后,渲染器就可以完成组件的渲染,如下面的代码所示:

js 复制代码
// 用来描述组件的 VNode 对象,type 属性值为组件的选项对象
const CompVNode = {
  type: MyComponent
}
// 调用渲染器来渲染组件
renderer.render(CompVNode, document.querySelector('#app'))

渲染器中真正完成组件渲染任务的是 mountComponent 函数,其主要代码逻辑如下:

  1. 通过虚拟节点(vnode) 获得组件的选项对象,即 vnode.type

  2. 通过组件的选项对象获得渲染函数 render

  3. 执行渲染函数,获取组件要渲染的内容,即 render 函数返回的虚拟 DOM

  4. 最后调用 patch 函数来挂载组件所描述的内容,即 subTree

js 复制代码
function mountComponent(vnode, container, anchor) {
  // 通过 vnode 获取组件的选项对象,即 vnode.type
  const componentOptions = vnode.type
  // 获取组件的渲染函数 render
  const { render } = componentOptions
  // 执行渲染函数,获取组件要渲染的内容,即 render 函数返回的虚拟 DOM
  const subTree = render()
  // 最后调用 patch 函数来挂载组件所描述的内容,即 subTree
  patch(null, subTree, container, anchor)
}

真实的 Vue3 源码中的 mountComponent 函数实现会复杂很多,但是原理不变。

这样,就实现了最基本的组件化方案。

总结

当页面很复杂的时候,组件化可以将页面分成很多个部分,不同的部分就是不同的组件,这样便于维护。对于 Vue.js 的使用者来说,组件其实就是对象,而在 Vue.js 渲染器内部,组件是特殊类型的虚拟节点。

每个组件都有 render 函数,渲染器通过调用 render 函数获得该组件需要渲染的视图。

回到一开始的问题,Vue3 是如何渲染组件的?

渲染器根据虚拟节点的 type 属性判断是否为组件,如果是组件,渲染器会使用 mountComponent 和 patchComponent 来完成组件的挂载和更新。

参考

《Vue.js 设计与实现》霍春阳·著

相关推荐
new出一个对象2 小时前
uniapp接入BMapGL百度地图
javascript·百度·uni-app
你挚爱的强哥3 小时前
✅✅✅【Vue.js】sd.js基于jQuery Ajax最新原生完整版for凯哥API版本
javascript·vue.js·jquery
y先森3 小时前
CSS3中的伸缩盒模型(弹性盒子、弹性布局)之伸缩容器、伸缩项目、主轴方向、主轴换行方式、复合属性flex-flow
前端·css·css3
前端Hardy3 小时前
纯HTML&CSS实现3D旋转地球
前端·javascript·css·3d·html
susu10830189113 小时前
vue3中父div设置display flex,2个子div重叠
前端·javascript·vue.js
IT女孩儿4 小时前
CSS查缺补漏(补充上一条)
前端·css
吃杠碰小鸡5 小时前
commitlint校验git提交信息
前端
天天进步20156 小时前
Vue+Springboot用Websocket实现协同编辑
vue.js·spring boot·websocket
虾球xz6 小时前
游戏引擎学习第20天
前端·学习·游戏引擎
我爱李星璇6 小时前
HTML常用表格与标签
前端·html