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 设计与实现》霍春阳·著

相关推荐
她似晚风般温柔7892 小时前
Uniapp + Vue3 + Vite +Uview + Pinia 分商家实现购物车功能(最新附源码保姆级)
开发语言·javascript·uni-app
Jiaberrr3 小时前
前端实战:使用JS和Canvas实现运算图形验证码(uniapp、微信小程序同样可用)
前端·javascript·vue.js·微信小程序·uni-app
everyStudy3 小时前
JS中判断字符串中是否包含指定字符
开发语言·前端·javascript
城南云小白3 小时前
web基础+http协议+httpd详细配置
前端·网络协议·http
前端小趴菜、3 小时前
Web Worker 简单使用
前端
web_learning_3213 小时前
信息收集常用指令
前端·搜索引擎
Ylucius3 小时前
动态语言? 静态语言? ------区别何在?java,js,c,c++,python分给是静态or动态语言?
java·c语言·javascript·c++·python·学习
tabzzz3 小时前
Webpack 概念速通:从入门到掌握构建工具的精髓
前端·webpack
LvManBa4 小时前
Vue学习记录之六(组件实战及BEM框架了解)
vue.js·学习·rust
200不是二百4 小时前
Vuex详解
前端·javascript·vue.js