+-
Vue3.0 前瞻 - 新版本有哪些改动?

大家好,我们是明源云链前端团队。

在未来的日子里,我们将持续分享公司的技术沉淀,点波 关注 不迷路!

 一、Vue 的框架演进历程

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

image

Vue2.x 发展了很久,期间各个生态都已经很完善了,且有着很多优秀项目案例,对于使用 Vue2.x 的开发者来说,已经能够满足到日常开发的所有需要。你可能会觉得这是一款非常优秀的框架了,然而在做着右小右的眼中,依然有些许不足。

Vue2.x 到 Vue3.0,解决了之前存在的一些痛点,比如源码自身的维护性,数据量大后带来的渲染和更新的性能问题,一些想舍弃但为了兼容一直保留的鸡肋 API 等;另外,作者还希望能给开发人员带来更好的编程体验,比如更好的 TypeScript 支持、更好的逻辑复用实践等,所以他希望能从源码、性能和语法 API 三个大的方面优化框架。

image

 二、 Vue3.0 的优化点

 1. 源码优化

源码的优化主要体现在使用 monorepo 和 TypeScript 管理和开发源码,这样做的目标是提升自身代码可维护性  。

Vue2.x 的项目机构,代码托管于 src 目录下,并分成以下几个模块。


src

 |- compiler # 模板编译的相关代码

 |- core # 与平台无关的通用运行时代码

 |- platforms # 平台专有代码

 |- server # 服务端渲染的相关代码

 |- sfc # .vue 单文件解析相关代码

 |- shared # 共享工具代码

Vue3.0 使用 monorepo 的项目管理方式,将代码托管于 packages 目录,各个模块代码职责单一,有着各自的 API、类型定义和完善的测试用例。使得代码维护更加方便吗,单独引用 Vue 响应式的功能也更加方便,可以直接引入 reactivity 模块,而不需要直接引入整个包,减少了包体积的大小,这点在 Vue2.x 上是做不到的。


packages

 |- compiler-core

 |- compiler-dom

 |- compiler-sfc

 |- compiler-ssr

 |- reactivity

 |- runtime-core

 |- runtime-dom

 |- shared

 |- vue

 |- ...

 2. 更好的类型支持:Typescript

Vue3.0 使用 typescript 重构了整个项目,对于类型支持更加友好。

使用类型语言非常有利于代码的维护,可以在开发阶段就避免到很多不必要的错误,也可以借助 IDE 的类型推导能力帮助我们更好的识别参数类型。

在 Vue2.x 中使用了 Flow,首先 Flow 是 Facebook 出品的 javascript 的静态类型检测工具,可以以非常小的成本对已有的 javascript 代码迁入,非常灵活。不过由于迭代的进行发现对于一些较为复杂的类型无法很好的支持。比如我们阅读 Vue2.x 的源码可以看到在代码注释中对 Flow 的吐槽:


const propOptions: any = vm.$options.props; // wtf flow?

为什么会这样呢?这是因为 FLow 并没有正确的推导出 vm.$options.props 的类型,开发人员不得不将 propOptions 类型强制的设置为 any,显得很不合理。

Vue3 中全部源码使用 Typescript 进行重构,typescript 对于复杂的应用场景能更有利于代码的维护,而就目前的生态来看,typescript 团队也越做越好,支持更多的功能,更好的弥补了 js 的缺陷。

 3. 性能优化

性能优化主要体现在以下几点:


1. 源码体积优化

2. 数据劫持优化

3. 编译优化

4. 语法 API 优化

 3.1 源码体积优化

Vue3  在源码体积的减少方面做了哪些工作呢?

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

 3.2 数据劫持优化

Vue.js 区别于 React 的一大特色是它的数据是响应式的,这个特性从 Vue.js 1.x 版本就一直伴随着,这也是 Vue.js 粉喜欢 Vue.js 的原因之一,DOM 是数据的一种映射,数据发生变化后可以自动更新 DOM,用户只需要专注于数据的修改,没有其余的心智负担。

image

 3.3 编译优化

Vue2.x VS Vue3.0 首次渲染对比,可以看到 Vue3.0 在渲染方面具有明显的优势,占据内存更小,渲染速度更快。

image

同样的,对于以下节点的渲染,在 diff 阶段也做了优化,我们知道,通过数据劫持和依赖收集,Vue2  的数据更新 并触发重新渲染的粒度是组件级的,虽然  Vue  能保证触发更新的组件最小化,但在单个组件内部依然需要遍历该组件的整个  vnode  树。

不过我们上面有许多的静态节点,只有一个动态节点,我们希望 text 变化时只更新动态节点的 diff,这样可以避免很多不必要 diff。然而,这点在 Vue2 中是做不到的。 因此,Vue3 中重写了 diff 算法,添加了属性标记,通过编译阶段对静态模板的分析,编译生成了  Block tree。 Block tree  是一个将模版基于动态节点指令切割的嵌套区块,每个区块内部的节点结构是固定的,而且每个区块只需要以一个  Array  来追踪自身包含的动态节点。

将如下代码进行编译后我们等到编译后的代码,这里推荐一个 vue3 的模板编译网站


<div>

 <p>

 一个静态节点

 <span @click="handleClick">子节点</span>

 </p>

 <p>

 一个静态节点

 <span>子节点</span>

 </p>

 <p v-show="show">一个静态节点</p>

 <p>动态节点{{text}}</p>

</div>

编译后:


import {

 createVNode as _createVNode,

 createTextVNode as _createTextVNode,

 vShow as _vShow,

 withDirectives as _withDirectives,

 toDisplayString as _toDisplayString,

 openBlock as _openBlock,

 createBlock as _createBlock,

} from "vue";

export function render(_ctx, _cache, $props, $setup, $data, $options) {

 return (

 _openBlock(),

 _createBlock("div", null, [

 _createVNode("p", null, [

 _createTextVNode(" 一个静态节点 "),

 _createVNode(

 "span",

 { onClick: _ctx.handleClick },

 "子节点",

 8 /* PROPS */,

 ["onClick"]

 ),

 ]),

 _createVNode("p", null, [

 _createTextVNode(" 一个静态节点 "),

 _createVNode("span", null, "子节点"),

 ]),

 _withDirectives(

 _createVNode("p", null, "一个静态节点", 512 /* NEED_PATCH */),

 [[_vShow, _ctx.show]]

 ),

 _createVNode(

 "p",

 null,

 "动态节点" + _toDisplayString(_ctx.text),

 1 /* TEXT */

 ),

 ])

 );

}

// Check the console for the AST

从上面可以看出 _createVNode 接受四个参数,第四个参数为编译的模板类型,在编译过程中会进行 diff ,如果第四个参数不传则会被忽略。假想一下,如果在一个组件中嵌套层级较深,且多为静态节点,这将会有多么大的性能提升啊。

 3.4 语法 API 优化: Compisition API

image

源码优化逻辑组织

Vue3.0 在逻辑复用方面具有强大的功能,相较于 Vue2.x 的 mixins 语法更加利于维护和管理,特别是在大型项目的应用中。能更好的支持逻辑的封装和复用。

image

以上是 Vue 作者在重构 Vue-cli UI 的一个例子,它是 Vue-cli UI 应用程序中的一个复杂的文件浏览器组件,这个组件需要处理许多不同的逻辑关注点:

跟踪当前文件夹状态并显示其内容 处理文件夹导航(比如打开、关闭、刷新等) 处理新文件的创建 切换显示隐藏文件夹 处理当前文件目录的修改

我们将逻辑关注点按照颜色区分,每一块颜色都是相对独立的逻辑封装。上面图中左侧部分是 Vue2.x 开发的,我们将 Vue2.x 中的 data、props、methods 等称为 Options API。相对的对比 Vue3.0 中的 Composition API。结果很明显,在左侧 Options API 中有两处紫色区域,这代码有部分逻辑分散开了,这就会导致我们开发过程中对任意一处的修改都将引起另一侧的变动,改动一个点需要上下反复横跳,非常难受。而使用 Composition API 则色块分明,逻辑独立,在代码较多时我们还可以很灵活的进行抽离,这就很利于后期维护。

优化逻辑复用

我们回顾下,在 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>

 <div>鼠标地址</div>

 <p>X坐标{{ x }} </p>

 <p>Y坐标{{ y }} </p>

 </div>

</template>

<script>

import Mixin from "./mixin";

export default {

 name: 'Mouse',

 mixins: [Mixin],

}

</script>

这似乎很好用,结构看起来也清晰,但是当我们的组件越写越大时,将会带来一些问题。首先每个 mixin 都可以定义自己的 props、data,它们之间是无感的,所以很容易定义相同的变量,导致命名冲突。另外对组件而言,如果模板中使用不在当前组件中定义的变量,那么就会不太容易知道这些变量在哪里定义的,这就是数据来源不清晰。

接下来我们再来看下使用 Vue3.0 的 Composition API 是如何实现的;


import { onMounted, onUnmounted, ref } from "vue";

function useMouse() {

 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 }

};

export default useMouse;

这里我们使用 React Hooks 的命名习惯,定义了个 useMouse,然后在组件中使用:


<template>

 <div>

 <div>鼠标地址</div>

 <p>X坐标{{ x }}</p>

 <p>Y坐标{{ y }}</p>

 </div>

</template>

<script>

import { defineComponent } from "vue";

import useMouse from "./hook";

export default defineComponent({

 name: "Mouse",

 setup() {

 const { x, y } = useMouse()

 return {

 x, y

 };

 },

});

</script>

可以看出使用 Composition API 编写的数据来源清晰了,即时创建更多的 Hooks 引入也不怕有冲突。Vue3 设计的 Composition API,就很好地帮助我们解决了 mixins 的这两个问题。

虽然 Composition API 有着诸多优势,但也不是说一点缺点都没有,我们将会在后续的源码解析章节中为大家进行说明。另外还需要说明的是,Composition API 属于 API 的增强,并没有强制要求使用,如果你的项目中没有太多需要且组件较为简单,你依然可以在 Vue3 中使用 Options API 去开发你的项目,

 后语

目前 Vue3.0 还未推出正式版,预计今年推出,不过 Vue3.0 的文档已经出来了,兴趣的可以到 Vue3 的官网(https://v3.vuejs.org/)查看。

最后需要说的是 Vue.js 3.0 使用 ES2015 的语法开发,有些 API 如 Proxy 是没有 polyfill 的,这就意味着官方需要单独出一个 IE11 compat 版本来支持 IE11。如果你的项目需要兼容 IE11,你就不得不小心使用某些 API,这也就带来了一些额外的心智负担。

下期预告:

Vue3 前瞻 - 全新的运行机制

 最后一件事

如果您已经看到这里了,希望您还是点个赞再走吧~

明源云链技术团队长期招募优秀的小伙伴,欢迎感兴趣的小伙伴前来咨询哟~