解密Vue3.0的新特性

前言
尤大大早在2019年10月5日就开放了vue3的源码,时隔半年,于今年的4月17日发布了vue3.0的Beta版本,4月21日B站开直播,对vue3.0做分享总结,这一连串的操作,也算是兢兢业业,大写的优秀了。这篇文章重点来解密一下他在视频里所提及的一些新特性,包括性能方面的大幅度提升,Tree-shaking抖动,Fragments,Teleport,Suspense,自定义渲染API等,其中最重磅的莫过于Composition API了,虽然有那么点像React Hooks,但是气质上还是有很多不同的,下面会具体介绍。先附上Beta版源码地址:https://github.com/vuejs/vue-next#status-beta,供大家仔细研读。
Performance
Virtual Dom
相信使用过前端框架React的人,对虚拟Dom应该很熟悉了。它的核心思想就是抽象原生Dom,封装对应操作的API。其中的Diff算法算是核心难点。vue3.0这次优化主要就是在Diff算法上下了很多功夫。它是怎么来实现的呢?主要通过patchflag来标记不同类型的数据,来实现动态数据的动态更新,区分静态数据,这样只会在最开始的时候加载一次,后续编译过程中均直接略过,从而达到提升渲染速度的目的。效果是,upadate的性能提升了1.3~2倍,开启ssr的情况下可以提升2~3倍,所以在性能方面的提升还是很可观的。我们来看下一个代码示例:
代码
html
1 | <div id="app"> |
vue2的编译结果
1 | function anonymous() { |
每次vue3值变化的时候,所有的节点都会重新创建,显然性能是额外损耗的。
vue3的编译结果
1 | import { toDisplayString as _toDisplayString, createVNode as _createVNode, openBlock as _openBlock, createBlock as _createBlock } from "vue" |
我们注意到,vue3的动态节点,被标记了1,这个数字就是patchflag值,这里表示text类型。它的作用,主要是在之后的动态更新过程中,判断当前Dom节点是否有此标记,如果有,就update该节点,如果没有,就忽略不更新。如此,就能把性能损耗降到最低。尤其是在拥有大量静态节点的Dom结构里,性能提升会更显著。
patchflag标记
patchflag的值有很多,我们常用到的有text、props,组合类型和事件监听等。下面是代码里列举的:
1 | // 这里注释仅供参考。 |
patchflag的用途:
- 用来标记当前节点需要更新,静态节点没有这个参数,更新的时候直接略过
- 组合更新,比如:1-只更新显示文本,8-只更新属性,9-更新显示文本和属性
在每次Dom更新的时候,只更新带有patchflag值的节点,没有的直接略过。而且,无论Dom节点层级有多深,都同样适用。这样更新只用关注变化的内容,而不用再去关心那些静态数据。这么做节省了更多的内存,提升更新渲染的速度。是不是很实用。
其中值得提的一点,叫组合patchflag值。这种类型使用的是位运算的原理。举个例子说明一下:

图中,我们可以看到文本和属性的组合值,在按位与之后,就得到了patchflag值为9。其他同理,不做赘述。
静态提升
代码
html
1 | <div id="app"> |
编译结果
1 | import { createVNode as _createVNode, toDisplayString as _toDisplayString, openBlock as _openBlock, createBlock as _createBlock } from "vue" |
我们看到所有的静态节点都被拿到了渲染函数之外,这样做的好处就是,静态节点只用被创建一次,在以后的渲染的时候拿来复用就行。可以极大的优化大型项目的内存占用,不用每次渲染都要重新创建。
事件监听缓存cacheHandlers
我们知道在vue2中,针对节点绑定的事件,每次触发都要重新生成全新的function去更新,而React目前也没有去缓存事件,具体为什么不去做,不得而知。在vue3中,就做了这么一件事情。当cacheHandlers开启的时候,编译会自动生成一个内联函数,将其变成一个静态节点,相当于React中的useCallback。并且本身就支持手写内联函数,这点就比较方便。我们来看下代码示例:
代码
html
1 | <div id="app"> |
开启cacheHandlers
1 | import { createVNode as _createVNode, toDisplayString as _toDisplayString, openBlock as _openBlock, createBlock as _createBlock } from "vue" |
关闭cacheHandlers
1 | import { createVNode as _createVNode, toDisplayString as _toDisplayString, openBlock as _openBlock, createBlock as _createBlock } from "vue" |
可以很清楚的看到其中的差别,开启cacheHandlers事件缓存之后,生成内联函数_cache,这样每次就不用重复渲染了,在事件更新频繁或者绑定事件过多的情况下,性能优化非常显著。
SSR
这个原理其实很简单,就是把所有的静态内容编译成一个字符串push到一个buffer里。
代码
html
1 | <div id="app"> |
编译结果
1 | import { ssrInterpolate as _ssrInterpolate } from "@vue/server-renderer" |
是不是很神奇,我们看到所有的静态节点span全部被编译成了一个字符串,甚至里面的动态节点也被尽可能的字符化。这样做至少可以节省一半以上内存,总体渲染速度加快一倍以上。SSR服务端渲染在性能上,比单纯的抽成Virtual Dom去渲染,快的不是一个量级,两者完全不在一个层面上。
这里要注意一点的是,如果静态节点数量很多,超过了一个阈值,vue3会单独创建一个div,将静态节点innerHTML到该div里去。
Tree-shaking
这里有很多解释,我更偏向于理解成,把没用的树叶”抖掉“这个比喻。
换句话说,就是按需引入我们需要的模块。对于用不到的模块就不会打包。
代码
html
1 | <div id="app"> |
编译结果
1 | import { vModelCheckbox as _vModelCheckbox, createVNode as _createVNode, withDirectives as _withDirectives, openBlock as _openBlock, createBlock as _createBlock } from "vue" |
这里在编译阶段,仅仅引入了vModelCheckbox一个Dom相关模块,其他模块都是通用的必须要引入的。这样打出的包会体积更小,速度更快,性能更好。这一点在vue2里是没有做区分的。
Composition API
终于到了vue3.0版本里最重磅的内容。与React Hooks类似,只是实现方式,整体气质不同。
拥有以下特性
- 兼容现有的Options API,无缝衔接
- 更加灵活,组合更方便,复用性更强
- 可以跟其他框架搭配使用
我们知道,vue3采用RFC来讨论所有的API的实现,并且把所有讨论的细节都呈现给使用者,我觉得这点是非常棒的事情。可以更友好的让开发者理解和学习API的使用,附上文档学习地址:https://composition-api.vuejs.org/#summary。
那么,为什么vue3必须要有Composition API这样的东西呢?下面我们看下几个场景,你就能理解Composition API的强大了。
Mixin

图中我们可以看到在解决API复用上,Mixin是可以达到的。但是它最要命的就是命名空间冲突的问题,这个缺陷可以导致代码的不可维护,这点对于开发者是不能接受的,甚至毁灭性的。
Scoped Slots

Scoped Slots虽然可以解决Mixin的问题,但是它的问题就是配置太多,逻辑也太多,这样就导致它的复用性和灵活性极差,还不如Mixin。
最后,我们来看看Composition API是怎么实现的。
Composition Functions

是不是很棒,使用了我们熟悉的函数式编程,结构非常清晰,代码量也减少了很多,最重要的是灵活性和复用性很友好。也不用担心会命名冲突的问题。
组件中的使用

图中不同颜色代表不同逻辑的业务代码,相同颜色表示同一业务需要的代码。我们在写一个比较复杂的组件的时候,有没有这种体会。组件里面包含很多个小的功能组件,这些功能可能在其他页面也有用到,但是数据强依赖这个组件的接口。所以只能分开,单独写到这个组件里,导致组件代码又臭又长,还没法复用。遇到这种情况,我们也只能望洋兴叹。在vue3.0出来之后,我们就可以完美的解决这种问题。我们可以任意在同一组件里,写不同的业务逻辑,不仅层次分明,逻辑清楚,而且神奇的是,你可以信手拈来,拿去复用在其他组件里。你可能也不再需要为了一个页面,去封装十几个小组件了。费神费力,还容易出错。
使用Composition API写出的代码,后期筛查问题、维护和update上,体验也是极度舒适的。
当然,我们不用担心之前的Options API不能用,vue3做了兼容,可以一起使用的。建议有了它就不要再考虑使用其他方案了。
具体使用方法可以查看文档:https://composition-api.vuejs.org/#summary
Fragments
碎片,这个我理解是不再限制组件中必须提供根节点。你可以这么写
1 | <div>vue3</div> |
也可以这么写
1 | vue3 |
其实看编译的代码,你会发现,vue3只是帮你生成了一个根节点来包裹。这么做的好处就是你省事了,腾出手来做你喜欢做的,vue3就像你的爱人一样,默默地帮你做了许多。
Teleport
这个特性,我理解相当于”占位“。看下代码
1 | <!-- 占位,专门用来显示定制内容 --> |
这里,id是modal-container的模块会在根页面里占位,在调用的时候插入
我认为这个特性不仅仅这么简单,其他应用场景有待挖掘。
Suspense
翻译为,”悬念“。我理解就是异步加载依赖。
用法
- 可在嵌套层级中等待嵌套的异步依赖项
- 支持 async setup()
- 支持异步组件
代码示例
1 | // vue2实现 |
这里实现一个过渡效果,使用Suspense实现,更简单直接一些。
这个特性的应用场景,也可能是图片或者模块的顺序加载。对于业务逻辑比较复杂,需要异步加载静态节点的时候,可以考虑使用。
Custom Renderer API
自定义渲染API这块,意味着以后我们可以通过 vue ,实现用 dom 编程的方式来进行 webgl 编程。
其他的一些变化
1 | // 周期函数变化 |
更好的TypeScript支持
vue3.0版本里,采用了TypeScript重写,意味着之后会全面拥抱TypeScript。类型提示、检测都更加的友好强大。废弃了Class,如果你还是喜欢Class的用法,可以安装单独库vue-class-component支持。
结语
以上对vue3主要的新特性做了简单的总结,也加入了一些个人的理解,目前还不能放手使用,只能算尝鲜阶段,所以如果想更深入了解,要仔细研读下vue3的源码。尤大大在直播问答环节,表示可能需要年终才会发布正式版本,所以还有一段时间来完善框架,让vue3更饱满的呈现给众人。后续还需要做的工作主要包括,文档编写,完善开发者工具,以及自动化迁移工具等。有大量的工作要做,是为了给vue2升级vue3做准备。尤大大在直播中也提及,未来会有一个过渡版本发布,这也是最后一个vue2版本vue2.7,计划在不损坏兼容性的前提下,加入一些好的vue3的功能,把它们提前合并入2.7版本,提供给开发者使用体验。而在这之后18个月,表示只会进行安全性的描述,不在继续维护。
而关于vue2升级到vue3,作者也友情提示大家,对于业务逻辑偏重的项目,请一定酌情考虑升级,自觉规避风险,言下之意就是说在全面拥抱vue3之前,可能你需要做一些取舍。
最后,我猜想,Composition API是有极大可能提前加入到2.7版本,提前让大家体验,那就让我们敬请期待吧。