[vue3] vue3对比vue2优化项简要概述

不足与展望

Vue2的不足

  1. 源码自身的可维护性较差;
  2. 数据量大后带来渲染和更新的性能问题;
  3. 存在一些为了兼容但是和鸡肋的API。

对Vue3的期望

  1. 更好的编程体验;
  2. 更好的TypeScript支持;
  3. 更好的逻辑复用实践。

源码性能语法API三大方面优化框架。

Vue3的优化

源码优化

源码优化的目的是让代码更易于开发和维护。

主要体现在使用monorepo管理源码仓库,以及使用TypeScript进行开发。

monorepo

monorepo将模块拆分到不同的package中,每个package拥有各自的API、类型定义和测试。

这样使得模块拆分更细化,职责划分更明确,模块之间的依赖关系也更加明确。

开发人员也更容易阅读、理解和更改所有模块源码,提高了代码的可维护性

使用mororepo还有一个好处是:package可以独立于Vue.js使用,例如reactivity响应式库。

如果用户仅需要使用Vue.js 3.0的响应式功能,可以单独依赖这个package而不需要依赖整个Vue.js减小了引用包的体积大小

TypeScript

使用TypeScript的好处是:

  • 可以在编码阶段做类型检查;
  • 定义接口类型,有利于IDE对变量类型的推导,开发更轻松。

Vue曾经用过flow作为类型工具,但后来转TypeScript了,原因在于:

  • TypeScript的社区更活跃;
  • TypeScriptIDE中的支持更好;
  • TypeScript有明确的版本发布策略,使得维护和升级更加可控,Flow的团队后期烂尾。

一篇相关的文章:Flow vs Typescript: From Flow to Typescript. Why? - DEV Community

性能优化

减小包体积

  1. 移除一些冷门的feature,比如inline-template
  2. 引入tree-shaking技术。
tree-shaking

tree-shaking依赖ES2015的模块语法,通过编译阶段的静态分析,找到没有引入的模块并打上标记。

在项目中没有引入的模块或组件,它们对应的代码就不会被打包,也就间接减小了项目引入Vue.js包体积的目的。

数据劫持优化

Vue最独特的特性之一,是其非侵入性的响应式系统。

实现非侵入式的响应式系统的关键在于拦截对数据的读写操作

响应式数据需要配置gettersetter。当渲染函数执行时,读取了数据,触发getter,在getter中将object[key]到依赖的映射记录到Watcher中。

依赖是一个函数,对于在template中使用的响应式数据来说,它的依赖就是渲染函数。

当响应式数据被更新时,即被赋值,会触发setter,在setter中根据objectkeyWatcher中找到相对应的依赖函数,执行。即触发了视图更新。

总结

  • getter中使用track函数收集依赖;
  • setter中使用trigger函数触发更新。

Vue2的做法

Vue2中使用Object.defineProperty这个方法拦截对对象指定属性的读写操作。

Object.defineProperty为对象的指定属性设置gettersetter

对于一个嵌套层级较深的对象来说,为了遍历对象的每个属性,需要递归调用,为每个属性都设置getter&setter

缺点

  • 递归调用会导致性能较差;

  • 无法检测 property 的添加或移除,后来新增的属性无法实现响应式;

    需要使用全局的Vue.set方法或者组件内部局部的this.$set方法,让Vue有能力将依赖添加到Watcher中进行管理。

  • 使用索引设置数组的某一项的值,无法被监测到;

  • 修改数组的length属性,不会触发更新;

  • 无法应对MapSet这些集合类型的响应式,需要开发者自己想办法解决。

这些缺点都来自于Object.defineProperty这个API本身的局限性。

Vue3的做法

Vue3中,数据拦截使用了ES6Proxy API,这个API可以直接将目标对象作为整体,拦截对它的各种操作,包括:

  • 对某个属性的读/写;
  • 新增属性;
  • 删除属性;
  • Proxy可以Map/Setgetter拦截到对get/set/add等方法的调用。

因此,Vue3相比于Vue2

  • 解决了递归配置getter&setter的问题;
  • 解决了新增/删除属性没有响应式的问题;
  • 实现了响应式的Map/Set

可以说Vue3在响应式这一部分的提升来自于ES6提供了更现代化更高效的API


编译优化

Vue2中,从new Vue()到生成DOM的过程大致如下:

graph TB A[new Vue] --> B[init] B --> C[$mount] C --> D[compile] D --> E[render] E --> F[vnode] F --> G[patch] G --> H[DOM]

上述的响应式过程发生在init阶段,而template编译为render function的阶段可以在打包过程中完成。

而在运行时中,耗时较多的是patch阶段。

Vue3在编译阶段优化了编译的结果,实现了对patch过程的优化。

具体来说是通过flag标记、静态提升的手段来实现的。

Vue2中,数据更新并触发重新渲染的粒度是组件级的。

在组件内部仍然需要遍历整颗VNode树,如果组件包含许多静态内容,那么diff算法会在静态内容上浪费许多时间。

也就是说diff算法的效率由组件的虚拟DOM树规模决定,而不是由动态节点的规模决定。

Vue3使用了Block Tree来优化diff过程。

Block Tree

Block Tree 是一个将模板基于动态节点指令切割的嵌套区块,每个区块内部的节点结构是固定的STABLE_FLAGMENT)。

对于结构固定的区块,仅需要使用一个Array来追踪其包含的动态节点。

借助 Block Tree,Vue3vnode 的更新性能调整为与动态内容的数量相关

其它

  • Vue3在编译阶段实现了对Slot的编译优化、事件侦听函数的缓存优化;
  • Vue3重写了diff算法。

语法API优化

Vue3在语法上的一个大的变动主要是提供了Composition API,即组合式API。

Vue2中,编写组件本质是在编写一个“包含了描述组件选项的对象”,被称为Options API,即选项式API。

选项式API适合开发小型组件,选项内容一目了然。对于包含多个逻辑关注点的大型组件来说,使用选项式API会导致一个逻辑关注点的代码被分散到不同选项中。

组合式API的好处在于可以将逻辑关注点相关的代码封装到一个函数里。

逻辑复用优化

Vue2中,存在mixins这种逻辑复用的方式。它的缺点在于当一个组件混入多个来源的mixins后,会出现命名冲突和数据来源不清晰的问题。

Vue3中,通过组合式函数解决了这一问题,组合式函数将可复用逻辑封装成函数(按照规范通常以use作为函数名开头),通常是返回一个对象。这是一种更灵活且更稳定的做法,因为:

  • 对于组合式函数来说,可以选择性地向外暴露API;
  • 对于使用地组件来说,解构返回的对象时,可以重命名避免命名冲突。

组合式API的其它好处

  • 更好的类型支持:组合式API都是函数,类型更容易推导,不想选项式API所有的内容都要通过this联系;
  • 组合式API对tree-shaking比较友好,代码也更容易压缩。

热门相关:赠我深爱如长风   勇闯天涯   神医娘亲:腹黑萌宝赖上门   龙皇缠身:爱妃,来生蛋!   南少,你老婆又跑了