Vue3 与 Vue2 区别
Vue3 与 Vue2 区别详述
1. 生命周期
对于生命周期来说,整体上变化不大,只是大部分生命周期钩子名称上 + “on”,功能上是类似的。不过有一点需要注意,Vue3 在组合式API(Composition API,下面展开)中使用生命周期钩子时需要先引入,而 Vue2 在选项API(Options API)中可以直接调用生命周期钩子,如下所示。
1 | // vue3 |
常用生命周期对比如下表所示。
vue2 | vue3 |
---|---|
beforeCreate | |
created | |
beforeMount | onBeforeMount |
mounted | onBeforeMount |
beforeUpdate | onBeforeMount |
updated | onUpdated |
beforeDestroy | onBeforeUnmount |
destroyed | onUnmounted |
Tips: setup 是围绕 beforeCreate 和 created 生命周期钩子运行的,所以不需要显式地去定义。
2. 多根节点
Vue2中,在模板中如果使用多个根节点时会报错,如下所示。
1 | // vue2中在template里存在多个根节点会报错 |
但是,Vue3 支持多个根节点,也就是 fragment。即以下多根节点的写法是被允许的。根节点不再需要用<div>
包裹.
1 | <template> |
3. Composition API
组合式API(Composition API)是vue3最大的特色。
Vue2 是选项API(Options API),一个逻辑会散乱在文件不同位置(data、props、computed、watch、生命周期钩子等),导致代码的可读性变差。当需要修改某个逻辑时,需要上下来回跳转文件位置。
Vue3 组合式API(Composition API)则很好地解决了这个问题,可将同一逻辑的内容写到一起,增强了代码的可读性、内聚性,其还提供了较为完美的逻辑复用性方案。
4. 异步组件(Suspense)
Vue3 提供 Suspense 组件,允许程序在等待异步组件加载完成前渲染兜底的内容,如 loading ,使用户的体验更平滑。使用它,需在模板中声明,并包括两个命名插槽:default 和 fallback。Suspense 确保加载完异步内容时显示默认插槽,并将 fallback 插槽用作加载状态。
1 | <tempalte> |
5. Teleport
Vue3 提供 Teleport 组件可将部分 DOM 移动到 Vue app 之外的位置。比如项目中常见的 Dialog 弹窗。
1 | <button @click="dialogVisible = true">显示弹窗</button> |
6. 响应式原理
Vue2 响应式原理基础是 Object.defineProperty;Vue3 响应式原理基础是 Proxy。
Object.defineProperty 基本用法:直接在一个对象上定义新的属性或修改现有的属性,并返回对象。
1 | let obj = {}; |
Tips: writable
和 value
与 getter
和 setter
不共存。
vue2核心源码
1 | function defineReactive(obj, key, val) { |
vue2响应式的缺陷是无法监听对象或数组新增、删除的元素。
vue3采用es6新特性Proxy Proxy ,通过第2个参数 handler 拦截目标对象的行为。相较于 Object.defineProperty 提供语言全范围的响应能力,消除了局限性。
vue3 的源码 reactive.ts 文件:
1 | function createReactiveObject(target, isReadOnly, baseHandlers, collectionHandlers, proxyMap) { |
7. 虚拟DOM
Vue3 相比于 Vue2,虚拟DOM上增加 patchFlag 字段。
1 | <div id="app"> |
渲染函数如下:
1 | import { createElementVNode as _createElementVNode, toDisplayString as _toDisplayString, openBlock as _openBlock, createElementBlock as _createElementBlock, pushScopeId as _pushScopeId, popScopeId as _popScopeId } from vue |
字段类型情况:1 代表节点为动态文本节点,那在 diff 过程中,只需比对文本对容,无需关注 class、style等。除此之外,发现所有的静态节点(HOISTED 为 -1),都保存为一个变量进行静态提升,可在重新渲染时直接引用,无需重新创建。
1 | // patchFlags 字段类型列举 |
8. 事件缓存
了解React的同学知道,使用 React 时,性能优化的其中一点就是将事件侦听方法手动进行缓存,避免更新组件时重复创建。而 Vue3 直接替我们做了这一步。
1 | <div id="app"> |
编译后的dom:
1 | const _hoisted_1 = { id: "app" } |
9.Diff算法优化
vue2.x中的虚拟dom是进行全量的对比,在运行时会对所有节点生成一个虚拟节点树,当页面数据发生变更好,会遍历判断virtual dom所有节点(包括一些不会变化的节点)有没有发生变化;虽然说diff算法确实减少了多DOM节点的直接操作,但是这个减少是有成本的,如果是复杂的大型项目,必然存在很复杂的父子关系的VNode,而Vue2.x的diff算法,会不断地递归调用 patchVNode,不断堆叠而成的几毫秒,最终就会造成 VNode 更新缓慢。
在Vue3.0中,在这个模版编译时,编译器会在动态标签末尾加上 /* Text*/ PatchFlag。也就是在生成VNode的时候,同时打上标记,在这个基础上再进行核心的diff算法并且 PatchFlag 会标识动态的属性类型有哪些,比如这里 的TEXT 表示只有节点中的文字是动态的。而patchFlag的类型也很多,其中大致可以分为两类:
- 当 patchFlag 的值「大于」 0 时,代表所对应的元素在 patchVNode 时或 render 时是可以被优化生成或更新的。
- 当 patchFlag 的值「小于」 0 时,代表所对应的元素在 patchVNode 时,是需要被 full diff,即进行递归遍历 VNode tree 的比较更新过程。
10. 打包优化
Tree-shaking:模块打包 webpack、rollup 等中的概念。移除 JavaScript 上下文中未引用的代码。主要依赖于 import 和 export 语句,用来检测代码模块是否被导出、导入,且被 JavaScript 文件使用。
以 nextTick 为例子,在 Vue2 中,全局API暴露在Vue实例上,即使未使用,也无法通过 tree-shaking 进行消除。
1 | import Vue from 'vue'; |
Vue3 中针对全局和内部的API进行了重构,并考虑到 tree-shaking 的支持。因此,全局API现在只能作为ES模块构建的命名导出进行访问。
1 | import { nextTick } from 'vue'; // 显式导入 |
通过这一更改,只要模块绑定器支持 tree-shaking,则Vue应用程序中未使用的 api 将从最终的捆绑包中消除,获得最佳文件大小。
受此更改影响的全局API如下所示。
- Vue.nextTick
- Vue.observable (用 Vue.reactive 替换)
- Vue.version
- Vue.compile (仅全构建)
- Vue.set (仅兼容构建)
- Vue.delete (仅兼容构建)
内部API也有诸如 transition、v-model 等标签或者指令被命名导出。只有在程序真正使用才会被捆绑打包。Vue3 将所有运行功能打包也只有约22.5kb,比 Vue2 轻量很多。
11. TypeScript支持
Vue3 由 TypeScript 重写,相对于 Vue2 有更好的 TypeScript 支持。
- Vue2 Options API 中 option 是个简单对象,而 TypeScript 是一种类型系统,面向对象的语法,不是特别匹配。
- Vue2 需要vue-class-component强化vue原生组件,也需要vue-property-decorator增加更多结合Vue特性的装饰器,写法比较繁琐。