一、写在前面
Vue.js 从 1.x 到 2.0 版本,最大的升级就是引入了虚拟 DOM 的概念,它为后续做服务端渲染以及跨端框架 Weex 提供了基础。Vue.js 2.x 发展了很久,现在周边的生态设施都已经非常完善了,而且对于 Vue.js 用户而言,它几乎满足了我们日常开发的所有需求。你可能觉得 Vue.js 2.x 已经足够优秀,但是在 Vue.js 作者尤小右的眼中它还不够完美。在迭代 2.x 版本的过程中,小右发现了很多需要解决的痛点,比如源码自身的维护性,数据量大后带来的渲染和更新的性能问题,一些想舍弃但为了兼容一直保留的鸡肋 API 等;另外,小右还希望能给开发人员带来更好的编程体验,比如更好的 TypeScript 支持、更好的逻辑复用实践等,所以他希望能从源码、性能和语法 API 三个大的方面优化框架。
那么接下来,我们就一起来看一下 Vue.js 3.0 具体做了哪些优化。相信你学习完这篇文章,不仅能知道 Vue.js 3.0 的升级给我们开发带来的收益,还能学习到一些设计思想和理念,并在自己的开发工作中应用,获得提升。
二、源码优化:2.1、更好的代码管理方式:monorepo
首先源码的优化体现在代码的管理方式上。vue2.x的源码管托在src目录中,然后依据功能拆分出了complier(模板编译的相关代码)
,core(与平台无关的通用运行时代码)
,platforms(平台专有代码)
,server
(服务端渲染的相关代码)、sfc
(.vue 单文件解析相关代码)、shared
(共享工具代码) 等目录。
而到了vue3.0,整个源码是通过monorepo
的方式维护的,根据功能不同的模块拆分到packages目录下面不同的子目录中。
可以看出相对于vue2.x的源码组织方式,monorepo把这些不同的package中,每一个package有各自的api,类型定义和测试,这样使得模块拆分更加细化,职责划分更明确,模块之间的依赖关系也更加明确,开发人员也更容易阅读、理解和更改所有模块源码,提高代码的可维护性。另外一点package可以独立于vue.js去使用,这样例如用户想要使用vue3.0的响应式,可以单独依赖reactive
,而不必依赖整个vue.js,减少引用包的体积,而vue2.x却做不到这一点。2.2、有类型的javascript——typescript
在vue1.x中当时没有采用类型语言。但是对于开发大型框架的时候,使用类型语言非常有利于IDE对于类型推导。所以尤雨溪在vue2.x使用的是Flow来进行开发,Flow是facebook出品的javascript的静态类型检查工具,但是flow对于一些复杂的场景flow支持的不是很好。所以在vue3.x中vue全面转向typescript,typescript提供了更好的类型检查,也支持复杂的类型推导。
三、性能优化3.1、源码体积优化
在性能优化方面我们首先想到的是代码的体积,因为javascript包的体积越小,意味着网络传输的时间就越短,javascript引擎解析包的速度也就越快。vue3.0在源码体积上做了哪些工作呢?
1、首先,移除了一些冷门的api(例如:filter, inline-template)等
2、其次,引入tree-shaking的技术来减少打包的体积。
第一点非常容易理解,但是第二点该如何去理解呢?下面举一个例子,一个math模块中定义两个方法square(x)和cube(x)。
export function square(x) {
return x * x
}
export function cube(x) {
return x * x * x
}
然后我们在这个模块外边引入cube方法
。
import { cube } from './math.js'
最终math模块会被webpack打包如下所示。
/* 1 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
'use strict';
/* unused harmony export square */
/* harmony export (immutable) */ __webpack_exports__['a'] = cube;
function square(x) {
return x * x;
}
function cube(x) {
return x * x * x;
}
});
我们可以看square
上存在一个标记,所以当我们使用丑化代码工具来对js代码进行丑化时,这些存在标记的代码就会被去除,从而减少代码的体积。
在vue3.x中也是同样的道理,利用tree-shaking
技术,如果项目中没有使用transition
,和keep-alive
等组件时,那么这些代码就不会进行打包,这样就间接达到了减少项目引入vue.js
包体积的目的。3.2、数据劫持优化
响应式是vue.js和react的差别,从vue1.x版本就一直伴随着。DOM是数据的一种映射,数据发生变化后就会自动更新DOM,用户只需要专注于数据的修改,没有其余的心智负担。
在vue.js内部如果想要实现响应式,则就会出现数据的劫持和更新,也就是当数据发生改变时,会自定执行一些代码去更新DOM。
如上图所示,当我们开始执行render时,会触发data中的getter方法,让后就会进行依赖收集,当数据发生改变的时候,此时就会触发notify来执行代码改变页面内容。(也就是重新渲染)。
在vue1.x和vue2.x内部是通过Object.defineProperty
这个API
来获取数据的getter
和setter
。具体是如下实现的。
Object.defineProperty(data, 'a', {
get(){
},
set() {
}
})
但是这个api只能检测get和set,不能检测给对象增加属性和删除属性,此时vue为了解决这个问题,出现了$set
和$delete
实例方法。另外还存在一个问题。就是如果存在多个对象进行嵌套问题。例如:
export default {
data: {
a: {
b: {
c: {
d: 1
}
}
}
}
}
由于vue.js无法判断你在运行时到底会访问那个属性,所以对于这样一个嵌套比较深的对象,就需要使用遍历吗,这无疑给性能增加了很大的负担。
为了解决如上问题,在vue3.0中使用来proxy api做数据劫持,它的内部是这样的。
observed = new Proxy(data, {
get() {
// track
},
set() {
// trigger
},
});
由于proxy劫持的是整个对象,那么对于对象的增加删除都是可以劫持到的。但是注意的是,Proxy api并不能检测到内部对象的变化,所以vue3.0的处理方式为在getter中去使用响应式递归,当真正需要响应式递归时,才会去递归,这样极大程度提升了性能问题。3.3、编译优化
最后是编译优化,为了方便理解,下面为一张图
这里vue.js2.x从new Vue开始渲染DOM的流程,之前我们通过数据劫持来进行优化,下面我们可以对耗时较多的patch阶段进行优化。我们都知道vuejs2.x的数据更新并触发重新渲染的颗粒是组件级别的。
虽然vue能保证呢触发更新的组件最小化,但是单个组件内部仍然需要遍历该组件的整个vnode树,举一个例子,例如我们需要更新如下的组件。
<template>
<div id="content">
<p class="text">static text</p>
<p class="text">static text</p>
<p class="text">{{message}}</p>
<p class="text">static text</p>
<p class="text">static text</p>
</div>
</template>
整个的diff算法是这样的:
可以看到,因为代码中只有一个动态节点,所以这里有很多的diff的遍历其实都是不需要的,也就是说导致vnode性能的跟模板大小正相关,跟动态节点的数量无关。当一些组件的整个模板中只有少量的动态节点时,这些遍历都是性能的浪费。对于上述例子中,理想状态只需要diff这个绑定message的p标签即可。vue3.0做到了,通过对编译阶段对静态模板进行分析,编译生层Block tree。借助 Block tree,Vue.js 将 vnode 更新性能由与模版整体大小相关提升为与动态内容的数量相关,这是一个非常大的性能突破。
四、语法API的优化——Composition API4.1、优化逻辑组织
在vue1.x和vue2.x时,我们使用的是Options api
,但是在vue3.x时,我们使用的是Componsition api
,因为在vue2.x时,使用Options api
如果处于比较小型的项目中,可能逻辑还是可以分清的,但是如果处于大型的项目中就需要上下来回的切换代码。如下图所示Options api 和 Componsition api对比
4.2、优化逻辑复用
当我们开发的项目比较复杂的时候,免不了需要抽离出来一些复用的路基,在vue2.x中我们通常会使用混入(mixins)去复用逻辑,举一个鼠标监听的例子,我们会编写如下代码。
const mousePositionMixin = {
data() {
return {
x: 0,
y: 0
}
},
mounted() {
window.addEventListener('mousemove', this.update)
},
destroyed() {
window.removeEventListener('mousemove', this.update)
},
methods: {
update(e) {
this.x = e.pageX
this.y = e.pageY
}
}
}
export default mousePositionMixin
然后在组件中使用:
<template>
<div>
Mouse position: x {{ x }} / y {{ y }}
</div>
</template>
<script>
import mousePositionMixin from './mouse'
export default {
mixins: [mousePositionMixin]
}
</script>
使用单个 mixin 似乎问题不大,但是当我们一个组件混入大量不同的 mixins 的时候,会存在两个非常明显的问题:命名冲突和数据来源不清晰。首先每个 mixin 都可以定义自己的 props、data,它们之间是无感的,所以很容易定义相同的变量,导致命名冲突。另外对组件而言,如果模板中使用不在当前组件中定义的变量,那么就会不太容易知道这些变量在哪里定义的,这就是数据来源不清晰。但是 Vue.js 3.0 设计的 Composition API,就很好地帮助我们解决了 mixins 的这两个问题。在vuejs3.x中使用hook来解决
import { ref, onMounted, onUnmounted } from 'vue'
export default function useMousePosition() {
const x = ref(0)
const y = ref(0)
const update = e => {
x.value = e.pageX
y.value = e.pageY
}
onMounted(() => {
window.addEventListener('mousemove', update)
})
onUnmounted(() => {
window.removeEventListener('mousemove', update)
})
return { x, y }
}
然后进行使用。
<template>
<div>
Mouse position: x {{ x }} / y {{ y }}
</div>
</template>
<script>
import useMousePosition from './mouse'
export default {
setup() {
const { x, y } = useMousePosition()
return { x, y }
}
}
</script>
我们可以看到,数据来源变得清晰了,即使去编写更多的hooks,也不会出现命名冲突的问题。Componsition api
除了在逻辑复用方面存在一些优势,也会有更好的类型支持,因为它们都是一些函数,在调用函数时,自然所有的类型就被推导出来了,不像 Options API 所有的东西使用 this。另外,Composition API 对 tree-shaking 友好,代码也更容易压缩。这里还需要说明的是,Composition API 属于 API 的增强,它并不是 Vue.js 3.0 组件开发的范式,如果你的组件足够简单,你还是可以使用 Options API。
五、引入RFC:使得每一个版本改动可控
作为一个流行开源框架的作者,尤雨溪可能每天都受到很多的feature request
。但是并不是社区一存在新的功能的需求,框架就会马上实现,因为随着 Vue.js 的用户越来越多,小右会更加重视稳定性,会仔细考虑所做的每一个可能对最终用户影响的更改,以及有意识去防止新 API 对框架本身实现带来的复杂性的提升。因此vuejs2.x版本开发到后期的阶段,尤雨溪就启动了RFC
,他的全称为Request for comments
。当社区有一些新需求的想法时,它可以提交一个 RFC,然后由社区和 Vue.js 的核心团队一起讨论,如果这个 RFC 最终被通过了,那么它才会被实现。到了vuejs3.0实现代码前就大规模启用 RFC,来确保他的改动和设计都是经过讨论并确认的,这样可以避免走弯路。Vue.js 3.0 版本有很多重大的改动,每一条改动都会有对应的 RFC,通过阅读这些 RFC,你可以了解每一个 feature 采用或被废弃掉的前因后果。
版权说明 : 本文为转载文章, 版权归原作者所有 版权申明
原文链接 : https://blog.csdn.net/weixin_47450807/article/details/123478024
内容来源于网络,如有侵权,请联系作者删除!