《Vue.js的设计与实现》快速阅读-第一篇

221 阅读9分钟

本书是Vue.js团队成员之一“霍春阳”所著,作者还有Vue3源码解读,为Vue3修补了大量补丁,对技术专注又投入。作者高度参与了Vue3建设工作,对技术细节的理解可靠,对Vue高层设计思想的理解也非常精准。本书致力于基于Vue设计思想,展开对Vue各模块的介绍和理解。综上,此书值得细细品读。 以下,我将在阅读过程中对书进行基础拆分,便于本文读者高效的阅读或复读。

一. 全书结构,分6篇

  1. 框架设计概览:先全局分析框架的选择、细节建设,最后全局介绍了设计思路、目前Vue3各模块之间的协作。(类似一个项目团队的建设过程,先结合项目特点确定组织架构,然后建设团队,每个工种的人员确定,最后全面介绍为什么选择这些人加入团队,以及团队如何协作)
  2. 响应系统:响应是Vue面世时的最大亮点,而Vue3响应的实现也是3和2的最大区别之一,面试常问。本书先全局介绍Vue3如何从0到1的实现响应———()—————,然后细节介绍实现过程中的重要技术点和难点。
  3. 渲染器:数据层变化-响应,之后就要“渲染”到页面层,否则用户看不到变化,白响应了。本篇先介绍了响应系统和渲染器是如何协作的(类似项目团队中不同工种之间的配合,比如前端和后台要互相响应配合),详细介绍了渲染器的基础信息:名词和概念、自定义如何实现。然后介绍了渲染器的实现过程中的技术重点:挂载和更新。最后介绍了3种算法工作原理,包括:简单Diff、双端Diff、快速Diff。
  4. 组件化:也是Vue面世时最大亮点。1.实现原理、组件的状态。2.异步组件和函数组件。3.Vue内建的3个重点组件:KeepAlive、Teleport、Transtion。
  5. 编译器:使用Vue开发是全js代码,如何转换成浏览器可识别的HTML等内容?就需要编译器了。1.Vue的模板编译器工作流程,parser、AST,然后输出了具体的生成渲染函数代码。2.HTML的解析器。3.优化:Vue3编译器的优化部分。
  6. 服务端渲染:Vue同构渲染的原理。先介绍了CSR、SSR及同构渲染的优缺点。然后探讨了Vue服务端渲染+客户端激活的原理。最后强调了编写同构代码的注意事项。

二. 按照“篇”和“章节”的内容拆分内容

第一篇 框架设计概览

框架设计时要权衡利弊,每一个环节都选择最合适的,互相协作。

第一章权衡的艺术

1.1 编程范式-命令式和声明式

命令式是原生js和JQuery的编程模式,符合我们的逻辑直觉。让代码从上到下一行行、一步步的,按顺序执行,而达成目标。
声明式是Vue和Reacte的使用模式,操作简单,代码简洁。当然了,Vue内部实现一定是命令式的,但是暴露给用户的已经是封装过的声明式了。
举例:点击按钮改变颜色。
这个行为用命令式实现:按钮点击后,判断状态,然后addClass或者removeClass。
用声明式:color = this.data.color; className = color ? !color : color;
声明式我们不关注细节,只确定规则。

在框架设计的时候,首先,我们需要在性能和可维护性之间权衡。
使用便捷和代码简洁方面,更偏好声明式。但是,“声明式代码的性能不优于命令式代码”。证明如下:
举例:修改标签内的文本内容。

命令式原生js:
div.textContent = 'hello vue3' // 直接修改

而声明式因为修改这个行为是根据结果的不同,来推断需要修改的内容,所以步骤反而更多:

1.先判断old代码与新代码的区别
2.确定需要修改的内容
3.使用div.textContent = 'hello vue3'修改一内容

那么,命令式与声明式消耗的性能为:
我们首先定义,修改文本消耗性能A,判断前后代码的不同消耗性能为B:
命令式:A
声明式:B+A 可见,声明式的性能消耗更大,但同时使用便捷且可维护好。这就是为什么说框架设计时要权衡利弊,在保证易使用和可维护的同时,尽量减少性能的消耗。

1.3虚拟DOM的性能到底如何

前文我们确定了一件事,声明式与命令式的性能消耗最大差距点在于找出差异的性能消耗,所以,如何最低耗能的找到差异,就是我们解决问题的点。
在修改标签文本时,innerHTML和虚拟DOM,两者的性能消耗如何?首先我们得知道这两种方式,创建、更新页面的过程。
*创建页面时
1.innerHTML创建的方式

InnerHTML
const html = `
    <div><span>...</span></div>
`;
div.innerHTML = html

这个innerHTML的过程,耗能是多少呢?我们知道上面代码中,耗能计算有JS层面的和DOM层面的,显然JS层面的耗能低很多,与DOM的耗能不是一个量级的。而innerHTML的过程需要消耗=JS字符串拼接的计算量+DOM计算量。
2.虚拟DOM创建的方式
使用虚拟DOM创建页面需要两步,第一步:有一个js对象,里面装着对真实DOM的描述。第二步:遍历虚拟DOM树,并创建真实DOM。
下面表格是对比二者创建页面的性能消耗

innerHTML虚拟DOM
js运算渲染HTML字符串创建js对象(VNode)
DOM运算新建所有DOM元素新建所有DOM元素

由此可见,js层面的性能消耗相差不大,DOM层面是一样的,因此在创建页面时,二者基本相同。然后,我们再对比“更新页面”的性能消耗。
1.innerHTML更新页面时
哪怕只更新一个字符,也要重新构建HTML字符串,然后重新设置DOM元素的innerHTML属性。而重新设置innerHTML属性,相当于销毁旧的DOM元素,全部新建新的DOM元素。
2.虚拟DOM更新页面
重新创建js对象,里面装着新的虚拟DOM树,然后对比新旧DOM树,找到变化的元素,再更新这个元素。
下面对比一下两种方式:

innerHTML虚拟DOM
js运算重新渲染HTML字符串新js字符串+Diff
DOM运算销毁所有旧DON元素,新建所有新DOM必要的DOM更新

在同一个数量级的计算,我们不列入对比,比如同js层面计算的差异我们选择忽略。可以发现,虚拟DOM会更新必要元素,innerHTMLu需要更换所有DOM

1.4 运行时和编译时

运行时:提供一个render函数,用户传入object对象就渲染出HTML标签。
编译时:提供一个compiler函数,用户传入多层HTML标签,自动编译出object数据对象。
这两种函数同时都有,就是运行时+编译时,Vue3采用的这个。

第二章 框架设计的核心要素

框架设计需要考虑很多因素,例如:

  • 2.1.用户使用体验,比如报错信息要人性化,console可以自定义
  • 2.2.代码体积尽量小,可以__DEV__区分开发和生产环境,这样打包的体积就有区别了
  • 2.3.良好的tree-Shaking,意思的dead code-永远不会执行的代码,它依赖于ES Module的静态结构。比如打包工具rollup.js会自动删除未使用函数,但是因为考虑到函数副作用,所以一般打包工具都提供一个机制明确这段代码可删除-例如注释代码/* #__PURE__ */
  • 2.4.该输出啥样的构建产物呢?vue.global.js开发环境,vuw=e.gglobal.prod.js生产环境。场景不同产物也不同。script标签可引入IIFE和ESM格式的资源。
    (1. 构建阶段需要立即调用-IIFE,(function(){}())
    (2. Vue3借助浏览器支持ESM而使用<script type="module" />,引入资源vue.esm-browser.js,定义__DEV__
    (3. 打包工具使用-bundler.js,定义process.env.NODE_ENV (4. 服务端渲染时,node环境require引入cjs-CommonJS,config.js的format=cjs
  • 2.5.给特性加开关,关闭的特性可以tree-shaking,比如vue3兼容选项式API,如果要关闭,用插件webpack.DefinePlugin实现optionsApi=false
  • 2.6.错误处理,替用户统一处理错误提示,handlerError变量,registerErrorHandler函数用户调用后会注册到变量上,所有的错误处理用callWithErrorHandler-执行变量内保存的具体错误提醒内容。
  • TypeScript支持,可以写ts和对其友好支持是不同的概念,源码中runtime-core/src/apiDefineComponets.ts,执行代码只有3行,但文件有200+行,都用于做类型支持

第三章 Vue3的设计思路

描述UI可以使用声明式和虚拟DOM,Vue都支持,前者直观省心后者灵活。渲染器调用原生jsAPI把虚拟DOM转为真实DOM,其优势在diff。组件本质是一个对象,包含组件虚拟DOM及其他相关信息。

  • 3.1 描述UI:声明式就是直接写标签,虚拟DOM就是用js。h函数是辅助创建虚拟DOM的工具函数,传参tag、props,比写完整的js对象省事点,return给组件渲染函数render就行。
  • 3.2 渲染器,寻找并只更新变化的内容够,把虚拟DOM渲染为真实DOM,h('div','hello')->真实DIV标签。
  • 3.3 组件的本质,有状态组件是对象,只有虚拟DOM的组件可以是函数,也使用编译器解析,可以理解为一组虚拟DOM的组合。
  • 3.4 各模块组合,组件和模板使用编译器、渲染器,相互影响&合作。v-bing绑定动态属性,编译器可以借助属性patchFlags说明哪些是动态属性。