vue3新特点

# 简介

Vue.js 从 1.x 到 2.0 版本,最大的升级就是引入了虚拟 DOM 的概念,它为后续做服务端渲染以及跨端框架 Weex 提供了基础;

# vue2.x的痛点

  • 源码自身的维护性;
  • 数据量大后带来的渲染和更新的性能问题;
  • 一些想舍弃但为了兼容一直保留的鸡肋 API 等;
  • TypeScript 支持;

# Vue2与Vue3的对比

  • 对TypeScript支持不友好(所有属性都放在了this对象上,难以推倒组件的数据类型)
  • 大量的API挂载在Vue对象的原型上,难以实现TreeShaking。
  • 架构层面对跨平台dom渲染开发支持不友好
  • CompositionAPI。受ReactHook启发
  • 对虚拟DOM进行了重写、对模板的编译进行了优化操作...
  • 从vue2中使用的Flow换成了Typescript;

# monorepo

# 简介

monorepo是一种将多个package放在一个repo中的代码管理模式

Vue3中 使用 yarn workspace + lerna 来管理项目

"workspaces": [
    "packages/*"
]
1
2
3

通过 workspaces 来指定需要管理的模块

# 管理源码

monorepo 把这些模块拆分到不同的 package 中,每个 package 有各自的 API、类型定义和测试。这样使得模块拆分更细化,职责划分更明确,模块之间的依赖关系也更加明确,开发人员也更容易阅读、理解和更改所有模块源码,提高代码的可维护性。

# 源码目录

vue-next
├─scripts
|    ├─build.js
|    ├─dev.js
|    ├─utils.js
├─packages
|    ├─global.d.ts
|    ├─vue
|    ├─template-explorer
|    ├─size-check
|    ├─shared
|    ├─server-renderer
|    ├─runtime-dom
|    ├─runtime-core
|    ├─reactivity
|    ├─compiler-ssr
|    ├─compiler-sfc
|    ├─compiler-dom
|    ├─compiler-core
├─test-dts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
  • reactivity:响应式系统
  • runtime-core:与平台无关的运行时核心 (可以创建针对特定平台的运行时 - 自定义渲染器)
  • runtime-dom: 针对浏览器的运行时。包括DOM API,属性,事件处理等
  • runtime-test:用于测试
  • server-renderer:用于服务器端渲染
  • compiler-core:与平台无关的编译器核心
  • compiler-dom: 针对浏览器的编译模块
  • compiler-ssr: 针对服务端渲染的编译模块
  • template-explorer:用于调试编译器输出的开发工具shared:多个包之间共享的内容vue:完整版本,包括运行时和编译器
  • shared:多个包之间共享的内容
  • vue:完整版本,包括运行时和编译器

# 个包关系

image-20211230115330626

                                    +---------------------+
                                    |                     |
                                    |  @vue/compiler-sfc  |
                                    |                     |
                                    +-----+--------+------+
                                          |        |
                                          v        v
                      +---------------------+    +----------------------+
                      |                     |    |                      |
        +------------>|  @vue/compiler-dom  +--->|  @vue/compiler-core  |
        |             |                     |    |                      |
   +----+----+        +---------------------+    +----------------------+
   |         |
   |   vue   |
   |         |
   +----+----+        +---------------------+    +----------------------+    +-------------------+
        |             |                     |    |                      |    |                   |
        +------------>|  @vue/runtime-dom   +--->|  @vue/runtime-core   +--->|  @vue/reactivity  |
                      |                     |    |                      |    |                   |
                      +---------------------+    +----------------------+    +-------------------+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# lerna

# 简介

lerna是在js项目中用来管理多个package的工具

  • 全局安装
npm install lerna -g
lerna init
1
2
  • 常用命令
lerna bootstrap # 安装依赖生成软链
lerna ls # 查看所有包
lerna publish # 发布包
1
2
3

# 实践

详见monorepo-lerna-yarn的实践

# 性能优化

# 源码体积优化

  • 首先,移除一些冷门的 feature(比如 filter、inline-template 等);
  • 其次,引入 tree-shaking 的技术,减少打包体积。

tree-shaking 依赖 ES2015 模块语法的静态结构(即 import 和 export),通过编译阶段的静态分析,找到没有引入的模块并打上标记。然后压缩阶段会利用例如 uglify-js、terser 等压缩工具真正地删除这些没有用到的代码。

例如,如果你在项目中没有引入 Transition、KeepAlive 等组件,那么它们对应的代码就不会打包,这样也就间接达到了减少项目引入的 Vue.js 包体积的目的。

# 数据劫持优化Proxy

Object.defineProperty 切换成 es6的Proxy实现

Proxy 劫持了我们对 observed 对象的一些操作,比如:

  • 访问对象属性会触发 get 函数;
  • 设置对象属性会触发 set 函数;
  • 删除对象属性会触发 deleteProperty 函数;
  • in 操作符会触发 has 函数;
  • 通过 Object.getOwnPropertyNames 访问对象属性名会触发 ownKeys 函数。

Proxy 劫持的是对象本身,并不能劫持子对象的变化,这点和 Object.defineProperty API 一致。但是 Object.defineProperty 是在初始化阶段,即定义劫持对象的时候就已经递归执行了,而 Proxy 是在对象属性被访问的时候才递归执行下一步 reactive,这其实是一种延时定义子对象响应式的实现,在性能上会有较大的提升。

# 编译优化

  • Vue.js 2.x 的数据更新并触发重新渲染的粒度是组件级的,虽然 Vue 能保证触发更新的组件最小化,但在单个组件内部依然需要遍历该组件的整个 vnode 树。
  • vue3做到动静分离,执行diff时仅对比动态节点。
  • 除此之外,Vue.js 3.0 在编译阶段还包含了对 Slot 的编译优化、事件侦听函数的缓存优化,并且在运行时重写了 diff 算法

# diff算法优化

深度递归遍历vnode树,节点的标签和key相同认为是同一个节点则更新,不同则删除,然后处理子节点。

子节点分这几种情况处理

纯文本、vnode 数组和空

  1. 空往往意味着添加或删除;
  2. 纯文本相同直接更新innerText,不同则删除;
  3. 新旧子节点都是vnode数组则diff算法来处理;

vue3.0 diff算法思想

  1. 编译模版时进行静态分析标记动态节点diff对比差异时仅对比动态节点(性能提升明显);
  2. diff算法先去头去尾,借此缩短遍历对比数组长度(对数组插入和删除操作性能优化明显);
  3. 通过对更新前后子节点数组建立映射表的方式,将O(n^2)复杂度的遍历降低到O(n);
  4. 通过最长递增子序列方法了来diff前后的子节点数组,减少移动操作的次数;

最长递增子序列算法实现:

/*
 * 寻找最长递增子序列
 * 使用动态规划思想,a -> c = a -> b + b -> c
 * 其中p数组存储的是从p[p[i]] 到 p[i] 的最长递增子序列索引,也就是前一个b的索引;
 * r数组存储最后一个元素也就是c的索引
 */
 function getSequenceOfLIS(arr) {
    const p = [0];
    const result = [0];
    for (let i = 0; i < arr.length; i ++) {
        const val = arr[i];
        const top = result[result.length - 1];
        if (arr[top] < val) {
            p[i] = top;
            result.push(i);
            continue;
        }
        // 二分法搜索
        let l = 0, r = result.length - 1;
        while(l < r) {
            const c = (l + r) >> 1;
            if (arr[result[c]] < val) {
                l = c + 1;
            } else {
                r = c;
            }
        }
        if (val < arr[result[l]]) {
            if (l > 0) {
                p[i] = result[l - 1]
            }
            result[l] = i;
        }
    }
    // 回朔p数组,找出最长递增子序列
    let preIndex = result[result.length - 1];
    for (let i = result.length - 1; i > 0; i --) {
        result[i] = preIndex;
        preIndex = p[preIndex]
    }
    return result;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42

# 语法 API 优化

# Composition API

# 优化逻辑组织

编写组件本质就是在编写一个“包含了描述组件选项的对象”,我们把它称为 Options API,符合直觉思维。

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

Composition API将某个逻辑关注点相关的代码全都放在一个函数里,这样当需要修改一个功能时,就不再需要在文件中跳来跳去。

# 优化逻辑复用

  • vue2 有mixin 变量命名容易冲突的问题
  • Composition API 显示的将变量引入当前组件,解决明明冲突问题
  • 简单组件使用Options API,复杂组件使用 Composition API

# Vue2/3 Diff算法区别

我们知道在数据变更触发页面重新渲染,会生成虚拟 DOM 并进行 patch 过程,这一过程在 Vue3 中的优化有如下

编译阶段的优化:

  • 事件缓存:将事件缓存(如: @click),可以理解为变成静态的了
  • 静态提升:第一次创建静态节点时保存,后续直接复用
  • 添加静态标记:给节点添加静态标记,以优化 Diff 过程

由于编译阶段的优化,除了能更快的生成虚拟 DOM 以外,还使得 Diff 时可以跳过"永远不会变化的节点",Diff 优化如下

  • Vue2 是全量 Diff,Vue3 是静态标记 + 非全量 Diff
  • 使用最长递增子序列优化了对比流程

根据尤大公布的数据就是 Vue3 update 性能提升了 1.3~2 倍

上次更新: 2022/04/15, 05:41:33
×