【Vue设计与实现】Vue.js 3 的 设计思路

前言

本章将从全局视角介绍 Vue.js 3.0 的设计思路,以及各个模块之间是如何协作的。

声明式的渲染UI

VueJS可以使用模板语法或JavaScript对象(或使用h函数将参数转化为JavaScript对象)来声明式

模板语法

JavaScript 复制代码
01 <h1 @click="handler"><span></span></h1>

JavaScript对象

JavaScript 复制代码
01 // h 标签的级别
02 let level = 3
03 const title = {
04   tag: `h${level}`, // h3 标签
05 }

h函数转化为JavaScript对象

h 函数是一个辅助创建虚拟 DOM 的工具函数,后续会将render()称为渲染函数,模板最终也会被编译器编译为渲染函数。

JavaScript 复制代码
01 import { h } from 'vue'
02
03 export default {
04   render() {
05     return h('h1', { onClick: handler }) // 虚拟 DOM
06   }
07 }

Vue源码中的 h 函数实现

初始渲染器

渲染器的作用就是把虚拟 DOM 渲染为真实 DOM。

实现一个简易的渲染器

JavaScript 复制代码
01 function renderer(vnode, container) {
02   // 使用 vnode.tag 作为标签名称创建 DOM 元素
03   const el = document.createElement(vnode.tag)
04   // 遍历 vnode.props,将属性、事件添加到 DOM 元素
05   for (const key in vnode.props) {
06     if (/^on/.test(key)) {
07       // 如果 key 以 on 开头,说明它是事件
08       el.addEventListener(
09         key.substr(2).toLowerCase(), // 事件名称 onClick ---> click
10         vnode.props[key] // 事件处理函数
11       )
12     }
13   }
14
15   // 处理 children
16   if (typeof vnode.children === 'string') {
17     // 如果 children 是字符串,说明它是元素的文本子节点
18     el.appendChild(document.createTextNode(vnode.children))
19   } else if (Array.isArray(vnode.children)) {
20     // 递归地调用 renderer 函数渲染子节点,使用当前元素 el 作为挂载点
21     vnode.children.forEach(child => renderer(child, el))
22   }
23
24   // 将元素添加到挂载点下
25   container.appendChild(el)
26 }

对于渲染器来说,它真正的难点在于需要精确地找到 vnode 对象的变更点并且只更新变更的内容。就上例来说,渲染器应该只更新元素对应改变的部分,而不需要再走一遍完整的创建元素的流程。

组件的本质

组件就是一组 DOM 元素的封装

我们可以定义一个JavaScript对象为MyComponent

JavaScript 复制代码
01 const MyComponent = function () {
02   return {
03     tag: 'div',
04     props: {
05       onClick: () => alert('hello')
06     },
07     children: 'click me'
08   }
09 }

在Vue源码中会判断vnode的tag是否是对象或是字符串,如果是字符串则直接作为标签渲染,如果是对象或是函数则进入组件渲染函数,组件渲染函数做的事情也就是递归调用渲染函数将标签拼装为组件。

JavaScript 复制代码
01 const vnode = {
02   tag: MyComponent
03 }
JavaScript 复制代码
01 function mountComponent(vnode, container) {
02   // vnode.tag 是组件对象,调用它的 render 函数得到组件要渲染的内容(虚拟 DOM)
03   const subtree = vnode.tag.render()
04   // 递归地调用 renderer 渲染 subtree
05   renderer(subtree, container)
06 }

模板的工作原理

以我们熟知的.vue 文件为例,一个 .vue 文件就是一个组件:

Vue 复制代码
01 <template>
02   <div @click="handler">
03     click me
04   </div>
05 </template>
06
07 <script>
08 export default {
09   data() {/* ... */},
10   methods: {
11     handler: () => {/* ... */}
12   }
13 }
14 </script>

其中 <template> 标签里的内容就是模板内容,编译器会把模板内容编译成渲染函数并添加到<\script> 组件对象上

JavaScript 复制代码
01 export default {
02   data() {/* ... */},
03   methods: {
04     handler: () => {/* ... */}
05   },
06   render() {
07     return h('div', { onClick: handler }, 'click me')
08   }
09 }

Vue.js的渲染过程

无论是使用模板还是直接手写渲染函数,对于一个组件来说,它要渲染的内容最终都是通过渲染函数产生的,然后渲染器再把渲染函数返回的虚拟 DOM 渲染为真实 DOM,这就是模板的工作原理,也是 Vue.js 渲染页面的流程。

Vue.js 是各个模块组成的有机整体

组件的实现依赖于渲染器,模板的编译依赖于编译器

举个例子,组件中id往往是不易改变的,而class确实会经常改变的,如果我们能在模板编译时区分出哪些是会经常改变的内容,将这些信息提取出来,然后直接交给渲染器。

JavaScript 复制代码
01 <div id="foo" :class="cls"></div>

我们一眼就能看出其中 id="foo" 是永远不会变化的,而:class="cls" 是一个 v-bind 绑定,它是可能发生变化的。所以编译器能识别出哪些是静态属性,哪些是动态属性,在生成代码的时候完全可以附带这些信息:

JavaScript 复制代码
01 render() {
02   return {
03     tag: 'div',
04     props: {
05       id: 'foo',
06       class: cls
07     },
08     patchFlags: 1 // 假设数字 1 代表 class 是动态的
09   }
10 }

总结

本章介绍了声明式描述 UI 的概念,强调了Vue.js作为声明式框架的优势,通过模板和虚拟DOM实现UI描述。渲染器的基本原理是将虚拟DOM渲染为真实DOM,采用Diff算法实现高效更新。对组件的讨论揭示了其本质是虚拟DOM元素的封装,可以是函数或对象,渲染器通过执行组件的渲染函数获取内容,并递归渲染。最后,强调编译器和渲染器是Vue.js的核心组成部分,它们协同工作以提高框架性能。

相关推荐
diemeng11192 分钟前
AI前端开发技能变革时代:效率与创新的新范式
前端·人工智能
bin91532 小时前
DeepSeek 助力 Vue 开发:打造丝滑的复制到剪贴板(Copy to Clipboard)
前端·javascript·vue.js·ecmascript·deepseek
晴空万里藏片云4 小时前
elment Table多级表头固定列后,合计行错位显示问题解决
前端·javascript·vue.js
曦月合一4 小时前
html中iframe标签 隐藏滚动条
前端·html·iframe
奶球不是球4 小时前
el-button按钮的loading状态设置
前端·javascript
kidding7234 小时前
前端VUE3的面试题
前端·typescript·compositionapi·fragment·teleport·suspense
Σίσυφος19006 小时前
halcon 条形码、二维码识别、opencv识别
前端·数据库
学代码的小前端6 小时前
0基础学前端-----CSS DAY13
前端·css
css趣多多7 小时前
案例自定义tabBar
前端
姑苏洛言8 小时前
DeepSeek写微信转盘小程序需求文档,这不比产品经理强?
前端