https://v3.cn.vuejs.org/guide/migration/introduction.html#%E6%A6%82%E8%A7%88
首先,为什么要优化?
Vue3 组合式 API(Composition API) 主要用于在大型组件中提高代码逻辑的可复用性。
vue2.x option Api的传统组件随着业务复杂度越来越高,代码量会不断的加大,整个代码逻辑都不易阅读和理解。
Vue3 使用组合式 API 的地方为 setup。
在 setup 中,我们可以按逻辑关注点对部分代码进行分组,然后提取逻辑片段并与其他组件共享代码。因此,组合式 API(Composition API) 允许我们编写更有条理的代码。

另,
compositon api
提供了以下几个函数:
setup
ref
reactive
watchEffect
watch
computed
toRefs
- 生命周期的
hooks
优化
一、 源码的优化
1. 更好的代码管理方式:monorepo
vue2.x | vue3.x |
---|---|
Vue.js 2.x 的源码托管在 src 目录,然后分别依据功能拆分出了: 1. compiler(模板编译的相关代码) 2. core(与平台无关的通用运行时代码) 3. platforms(平台专有代码) 4. server(服务端渲染的相关代码) 5. sfc(.vue 单文件解析相关代码) 6. shared(共享工具代码) 等目录: |
Vue.js 3.0 ,整个源码是通过 monorepo 的方式维护 根据功能将不同的模块拆分到packages 目录下面不同的子目录中 |
![]() |
![]() |
优点:
- 相对于 Vue.js 2.x 的源码组织方式,monorepo 把这些模块拆分到不同的 package 中,每个 package有各自的API、类型定义和测试。这样使得模块拆分更细化,职责划分更明确,模块之间的依赖关系也更加明确,开发人员也更容易阅读、理解和更改所有模块源码,提高代码的可维护性。
- 另外一些 package(比如 reactivity 响应式库)是可以独立于 Vue.js 使用的,这样用户如果只想使用 Vue.js 3.0 的响应式能力,可以单独依赖这个响应式库而不用去依赖整个 Vue.js,减小了引用包的体积大小,而 Vue.js 2 .x 是做不到这一点的。
2. 有类型的 JavaScript:TypeScript
vue2.x | vue3.x |
---|---|
Flow Flow 是 Facebook 出品的 JavaScript 静态类型检查工具 优点:它可以以非常小的成本对已有的 JavaScript 代码迁入,非常灵活 缺点:但是 Flow 对于一些复杂场景类型的检查,支持得并不好 |
TypeScript TypeScript提供了更好的类型检查,能支持复杂的类型推导 由于源码就使用 TypeScript 编写,也省去了单独维护 d.ts 文件的麻烦 |
二、 性能优化
1. 源码体积优化
- 移除一些冷门的
feature
(比如 filter、inline-template 等); - 引入
tree-shaking
的技术,减少打包体积。
tree-shaking原理
tree-shaking 依赖 ES2015 模块语法的静态结构(即 import 和 export),通过编译阶段的静态分析,找到没有引入的模块并打上标记。
举个栗子,一个 math 模块定义了 2 个方法 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'
// do something with cube
最终 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 技术,任何一个函数,仅仅在用到的时候才打包,没用到的模块都被摇掉,打包的整体体积变小。
2. 响应式系统——数据劫持优化

vue2.x中采用
Object.defineProperty
来劫持整个对象,然后进行深度遍历所有属性,给每个属性添加getter和setter,实现响应式:
- 缺点:它必须预先知道要拦截的 key 是什么,所以它并不能检测对象属性的添加和删除,所以提供了
vue.set
和vue.delete
方式进行处理,但是对于开发者来说显然是不那么友好的。
vue3.x采用proxy
重写了响应式系统,因为proxy可以对整个对象进行监听,所以不需要深度遍历:
- 可以监听动态属性的添加
- 可以监听到数组的索引和数组length属性
- 可以监听删除属性
3. 编译阶段的优化
- diff算法优化
- 静态提升
- 事件监听缓存
- SSR优化
3.1 diff算法优化
虽然 Vue 能保证触发更新的组件最小化,但在单个组件内部依然需要遍历该组件的整个 vnode 树,举个例子,比如我们要更新这个组件:
<div>
<p>老八食堂</p>
<p>{{ message }}</p>
</div>
则整个 diff 过程如图所示(在 Vue 2.x 的全量对比
模式下):

可以看到,这段代码中只有一个动态节点,所以这里有很多 diff 和遍历其实都是不需要的,这就会导致 vnode 的性能跟模版大小正相关,跟动态节点的数量无关,当一些组件的整个模版内只有少量动态节点时,这些遍历都是性能的浪费。
而对于上述例子,理想状态只需要 diff 这个绑定 message 动态节点的 p 标签即可。
在 Vue 3.0 中,对 diff 算法进行了优化,在创建虚拟 DOM 时,根据 DOM 内容是否会发生变化,而给予相对应类型的静态标记(PatchFlag)
,如下图所示:

观察上图,不难发现试图的更新只对带有 flag 标记的标签进行了对比(diff),所以只进行了 1 次比较,而相同情况下,Vue 2.x 则进行了 3 次比较。这便是 Vue 3.0 比 Vue2.x 性能好的第一个原因。
- 关于静态类型枚举如下:
export const enum PatchFlags {
TEXT = 1,// 动态的文本节点
CLASS = 1 << 1, // 2 动态的 class
STYLE = 1 << 2, // 4 动态的 style
PROPS = 1 << 3, // 8 动态属性,不包括类名和样式
FULL_PROPS = 1 << 4, // 16 动态 key,当 key 变化时需要完整的 diff 算法做比较
HYDRATE_EVENTS = 1 << 5, // 32 表示带有事件监听器的节点
STABLE_FRAGMENT = 1 << 6, // 64 一个不会改变子节点顺序的 Fragment
KEYED_FRAGMENT = 1 << 7, // 128 带有 key 属性的 Fragment
UNKEYED_FRAGMENT = 1 << 8, // 256 子节点没有 key 的 Fragment
NEED_PATCH = 1 << 9, // 512
DYNAMIC_SLOTS = 1 << 10, // 动态 solt
HOISTED = -1, // 特殊标志是负整数表示永远不会用作 diff
BAIL = -2 // 一个特殊的标志,指代差异算法
}
3.2 静态提升
Vue3中对不参与更新的元素,会做静态提升,只会被创建一次,在渲染时直接复用。
这样就免去了重复的创建节点,大型应用会受益于这个改动,免去了重复的创建操作,优化了运行时候的内存占用。
<span>你好</span>
<div>{{ message }}</div>
没有做静态提升之前:
export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createBlock(_Fragment, null, [
_createVNode("span", null, "你好"),
_createVNode("div", null, _toDisplayString(_ctx.message), 1 /* TEXT */)
], 64 /* STABLE_FRAGMENT */))
}
做了静态提升之后:
const _hoisted_1 = /*#__PURE__*/_createVNode("span", null, "你好", -1 /* HOISTED */)
export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createBlock(_Fragment, null, [
_hoisted_1,
_createVNode("div", null, _toDisplayString(_ctx.message), 1 /* TEXT */)
], 64 /* STABLE_FRAGMENT */))
}
// Check the console for the AST
静态内容_hoisted_1被放置在render 函数外,每次渲染的时候只要取 _hoisted_1 即可。
同时 _hoisted_1 被打上了 PatchFlag ,静态标记值为 -1 ,特殊标志是负整数表示永远不会用于 Diff。
3.3 事件监听缓存(cacheHandler)
默认情况下 @click 事件
被认为是动态变量,所以每次更新视图的时候都会追踪它的变化。但是正常情况下,我们的 @click 事件
在视图渲染前和渲染后,都是同一个事件,基本上不需要去追踪它的变化,所以 Vue 3.0 对此作出了相应的优化叫事件监听缓存,举个栗子:
<div>
<p @click="handleClick">屋里一giao</p>
</div>
编译后如下图所示(还未开启 cacheHandler):

在未开启事件监听缓存的情况下,我们看到这串代码编译后被静态标记为 8,之前讲解过被静态标记的标签就会被拉去做比较,而静态标记 8 对应的是“动态属性,不包括类名和样式”。
@click
被认为是动态属性,所以我们需要开启 Options
下的 cacheHandler
属性,如下图所示:
开启
cacheHandler
之后,编译后的代码已经没有静态标记(PatchFlag)
,也就表明图中 P 标签不再被追踪比较变化,也就是说下次diff算法的时候直接使用,进而提升了 Vue 的性能。3.4 SSR优化
当静态内容大到一定量级时候,会用createStaticVNode
方法在客户端去生成一个static node
,这些静态node,会被直接innerHtml
,就不需要创建虚拟DOM对象,然后根据对象渲染
<div>
<div>
<span>你好</span>
</div>
... // 很多个静态属性
<div>
<span>{{ message }}</span>
</div>
</div>
编译后
import { mergeProps as _mergeProps } from "vue"
import { ssrRenderAttrs as _ssrRenderAttrs, ssrInterpolate as _ssrInterpolate } from "@vue/server-renderer"
export function ssrRender(_ctx, _push, _parent, _attrs, $props, $setup, $data, $options) {
const _cssVars = { style: { color: _ctx.color }}
_push(`<div${
_ssrRenderAttrs(_mergeProps(_attrs, _cssVars))
}><div><span>你好</span>...<div><span>你好</span><div><span>${
_ssrInterpolate(_ctx.message)
}</span></div></div>`)
}
参考文献:
https://juejin.cn/post/6903171037211557895
https://vue3js.cn/interview/vue3/performance.html
https://juejin.cn/post/6850418112878575629#heading-5
网友评论