vue.js 3.0的优化

tech2026-04-10  0

vue.js 3.0主要做了哪些优化

  Vue.js从1.x到2.0版本,最大的升级就是引入了虚拟DOM的概念,它为后续做服务端渲染以及跨端框架Weex提供了基础。   Vue.js 2.0的不足之处:     1. 源码自身的维护性       数据量大后带来的渲染和更新的性能问题     2. 兼容性       为了兼容一直保留的鸡肋API

1. 源码优化

  对于Vue.js框架本身开发的优化,它的目的事让代码更易于开发和维护。   源码的优化主要体现在使用monorepo和TypeScript管理和开发源码,这样做的目的是提升自身代码可维护性

1. 更好的代码管理方式:monorepo

相对于Vue.js 2.x的源码组织方式,monorepo把这些模块拆分到不同的package中 每个package有各自的API、类型定义和测试 这样使得模块拆分更细化,职责划分更明确,模块之间的依赖关系也更加明确 开发人员也更容易阅读、理解和更改所有模块源码,提高代码的可维护性package(比如reactivity响应式库)是可以独立于Vue.js使用的 这样用户如果只想使用Vue.js 3.0的响应式能力,可单独依赖这个响应式库而不用去依赖整个Vue.js,减小了引用包的体积大小

2. 有类型的Javscript:TypeScript

编码期间做类型检查   避免一些应为类型问题导致的错误有利于它去定义接口的类型,利于IDE对变量类型的推导

Flow

Flow是Facebook出品的JavaScript静态类型检查工具,它可以以非常小的成本对已有的JavaScript代码迁入,非常灵活,这也是Vue.js 2.0当初选型它是一方面的考量Flow对于一些复杂场景类型的检查,支持并不好

2. 性能优化

1. 源码体积的优化

移除一些冷门的feature   Eg:filter、inline-template引入tree-shaking的技术,减少打包体积

tree-shaking

  依赖ES2015模块语法的静态结构(即import和export),通过编译阶段的静态分析,找到没有引入的模块并打上标记 Eg: math.js

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模块被标记了, 然后压缩阶段会利用例如uglify-js、terser 等压缩工具真正地删除这些没有用到的代码。   也就是说,利用tree-shaking 技术,如果你在项目中没有引入Transition、KeepAlive 等组件,那么它们对应的代码就不会打包,这样也就间接达到了减少项目引入的Vue.js包体积的目的。

2. 数据劫持优化

实现DOM功能,必须劫持数据的访问和更新    当数据改变后,为了自动更新DOM,就必须劫持数据的更新,也就是说,当数据发生改变后能自动执行一些代码去更新DOMVue.js怎么知道更新哪一片DOM呢?   因为在渲染DOM的时候访问了数据,我们可以对它进行访问劫持,这样就在内部建立了依赖关系,也就知道数据对应的DOM是什么了

  Vue.js 1.x和Vue.js 2.x内部都是通过 Object.defineProperty这个API去劫持数据的getter和setter,具体是这样的:

Object.defineProperty(data, 'a',{ get(){ // track }, set(){ // trigger } })

  但这个API有一些缺陷,它必须预先知道要拦截的key 是什么,所以它并不能检测对象属性的添加和删除。尽管Vue.js 为了解决这个问题提供了$set和$delete 实例方法,但是对于用户来说,还是增加了一定的心智负担。   另外Object.defineProperty 的方式还有一个问题,举个例子,比如这个嵌套层级比较深的对象:

export default { data: { A: { B: { C: { D: 1 } } } } }

  由于Vue.js 无法判断你在运行时到底会访问到哪个属性,所以对于这样一个嵌套层级较深的对象,如果要劫持它内部深层次的对象变化,就需要递归遍历这个对象,执行Object.defineProperty 把每一层对象数据都变成响应式的。毫无疑问,如果定义的响应式数据过于复杂,这就会有相当大的性能负担。   为了解决上述 2 个问题,Vue.js 3.0使用了Proxy API做数据劫持,它的内部是这样的:

observed = new Proxy(data, { get() { // track }, set() { // trigger } })

由于它劫持的是整个对象,那么自然对于对象的属性的增加和删除都能检测到。   但要注意的是,Proxy API 并不能监听到内部深层次的对象变化,因此 Vue.js 3.0 的处理方式是在getter中去递归响应式,这样的好处是真正访问到的内部对象才会变成响应式,而不是无脑递归,这样无疑也在很大程度上提升了性能

3.编译优化

  除了数据劫持部分的优化,我们可以在耗时相对较多的patch阶段想办法,通过在编译阶段优化编译的结果,实现运行时patch过程的优化。Vue.js 2.x的数据更新并触发重新渲染的粒度是组件级的;

Block tree

Block tree是一个将模板基于动态节点指令切割的嵌套区块,每个区块内部的节点结构是固定的,每个区块只需要一个Array来追踪自身的动态节点借助Block tree,Vue.js将vnode更新性能由与模块整体大小相关提升为与动态内容的数量相关

  除此之外,Vue.js 3.0 在编译阶段还包含了对Slot 的编译优化、事件侦听函数的缓存优化,并且在运行时重写了diff算法

4.语法API优化:Composition API

1. 优化逻辑组织

  在Vue.js 1.x和2.x版本中,编些组件本质就是在编写一个‘包含了描述组件选项的对象’,称为Options API

Option API
Option API的设计是按照methods,computed,data,props这些不同的选项分类当组件小的时候,这些分类方式一目了然;但是在大型组件中,一个组件可能有多个逻辑关注点,当使用Options API的时候,每个关注点都有自己的Options,如果要修改一个逻辑关注点,就需要在单个文件中不断上下切换和寻找

  在Vue.js 3.0中,提供了一中新的API:Composition API,就是将某个关注点相关的代码全都放在一个函数里面,这样当需要修改一个功能时,就不再需要在文件中跳来跳去

2.优化逻辑复用

  当开发项目变得复杂的时候,免不了需要抽象出一些复用的逻辑。   在Vue.js 2.x中,我们通常会用 mixins 去复用逻辑,举一个鼠标位置侦听的例子,我们会编写如下函数 mousePositionMixin:

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都可以定义自己的props、data,它们之间是无感的,所以很容易定义相同的变量,导致命名冲突对组件而言,如果模板中使用不在当前组件中的变量,那么就不会太容易知道这些变量在哪里定义的,这就是数据来源不清晰

  但是Vue.js 3.0设计的Composition API,就很好地帮助我们解决了mixins的这两个问题。

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 } }

这里我们约定useMousePosition这个函数为hook 函数,然后在组件中使用:

<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> 除了在逻辑方面有优势,也会有更好的类型支持因为它们都是一些函数,在调用函数时,自然有的类型就被推导出来了,不像Options API所有的东西都是使用thisComposition API对tree-shaking友好,代码也更容易压缩

Composition API属于API的增强,它并不是Vue.js 3.0 组件开发的范式,如果组件足够简单,还是可以使用Options API

5. 引入RFC:使每个版本改动可控

  RFC(Request For Comments),旨在为新功能进入框架提供一个一致且受控的路径

Vue.js 3.0中大规模启用RFC(Request For Comments),了解每一个feature采用或被废弃的原因

最新回复(0)