Vue3.0 从整体来说,既修改了 Vue2.x 时期遗留的一些缺陷,又引入了 组合式 API 、自定义渲染 API 等新特性。这些新特性的引入,不仅为 Vue 的发展注入了更多的活力,同时也塑造了 Vue 使用场景方面更多的可能性。
Vue3.0 的新特性
- Proxy 响应式绑定
- Tree-Shaking Support
- 组合式 API
- Fragment 片段、Teleport、Suspense
- 自定义渲染 API
- 源码优化
一、Proxy 响应式绑定
Vue2.x 内部是通过 Object.defineProperty
这个 API 去劫持数据的 getter 和 setter 来实现响应式的。因为这个 API 它必须预先知道要拦截的 key 是什么,所以它并不能检测对象属性的添加和删除。直接造成了数组元素的直接修改不会触发响应式机制,这个很多初学者所谓的 bug。
例如,对象obj的text属性进行劫持:
const obj = {};
Object.defineProperty(obj, 'text', {
get: function() {
console.log('get val'); 
},
set: function(newVal) {
console.log('set val:' + newVal);
document.getElementById('input').value = newVal;
document.getElementById('span').innerHTML = newVal;
}
});
const input = document.getElementById('input');
input.addEventListener('keyup', function(e){
obj.text = e.target.value;
})
Vue3.0 使用了 Proxy API 做数据劫持,它劫持的是整个对象,自然对于对象的属性的增加和删除都能检测到,自然也不会在存在上述的问题。
将上面例子进行改写:
const input = document.getElementById('input');
const p = document.getElementById('p');
const obj = {};
const newObj = new Proxy(obj, {
get: function(target, key, receiver) {
console.log(`getting ${key}!`);
return Reflect.get(target, key, receiver);
},
set: function(target, key, value, receiver) {
console.log(target, key, value, receiver);
if (key === 'text') {
input.value = value;
p.innerHTML = value;
}
return Reflect.set(target, key, value, receiver);
},
});
input.addEventListener('keyup', function(e) {
newObj.text = e.target.value;
});
通过 Proxy 实现双向响应式绑定,相比 defineProperty 的遍历属性的方式效率更高,性能更好,另外 Virtual DOM 更新只 diff 动态部分、事件缓存等,也带来了性能上的提升。
二、Tree-Shaking Support(摇树优化)
tree-sharking 即在构建工具构建后消除程序中无用的代码,来减少包的体积。
相比 Vue2.x 导入整个 Vue 对象,Vue3.0 支持按需导入,只打包需要的代码。Tree-Shaking 依赖 ES2015 模块语法的静态结构(即 import 和 export),通过编译阶段的静态分析,找到没有引入的模块并打上标记。像我们在项目中如果没有引入 Transition、KeepAlive 等不常用的组件,那么它们对应的代码就不会打包进去。
三、组合式 API
首先需要明确的是,Vue2.x 中组件传统的 data,computed,watch,methods 写法,我们称之为选项式 API(Options API )。
(1)选项式 API 存在的缺陷
随着业务复杂度越来越高,代码量会不断的加大;由于代码需要遵循 option 的配置写到特定的区域,导致后续维护非常的复杂,代码可复用性也不高。比如,很长的 methods 区域代码、data变量声明与方法区域未在一起。
(2)与 mixins 的比较
对于上述提到的问题,我们可能会想到会 mixins 来解决,但是当抽离并引用了大量的 mixins,你就会发现两个不可避免的问题:命名冲突和数据来源不清晰。
组合式 API 和 mixins 二者的差别:
- 层级不同:组合式 API 与组件是嵌套关系,而mixin与组件是同 层级关系
- 影响面不同:组合式 API 作为组件的被调用方,并且变量逻辑是组件控制,耦合性很低。而 mixins 是耦合在代码逻辑里,并且存在变量的互相引用,为将来的升级和维护埋下隐患。
(3)与 React Hook 的比较
不可置否,组合式 API 的诞生确实受到了 React Hooks 的启发。对于使用者而言,使用一些概念时大同小异,这也不用就此贴上抄袭的标签,因为很多语言上都有相似的概念或者语法,比如 TS 可以规范变量的声明,但是 Java 中为了方便也出现了 var。事实上,组合式 API 的实现与 React Hook 是截然不同的。
组合式 API 使用上 比 React Hooks简便了不少:
- 同样的逻辑组合、组件复用能力
- 只调用一次 setup 方法
更加符合 JS 直觉
没有闭包变量问题
没有内存/GC压力
不存在内联回调导致子组件永远更新的问题
(4)组合式 API 的使用
下面简单概述了组合式 API 的使用,主要是一些 方法增加 和 概念迁移:
-
setup方法:
vue3.0 中,所有的代码逻辑将在setup方法中实现,包括 data、watchcomputed、methods 等,并且不再有this。
vue3.0 会先执行setup方法,再执行兼容2.x的其他方法。
vue3.0 中setup方法在组件生命周期内只执行一次,不会重复执行。 -
生命周期钩子:
2.x 中生命周期钩子放在跟 methods 同级属性下。
3.x 中需要先导入钩子,然后在 setup 方法中注册钩子回调,并且钩子命名也跟 React 保持一样。
3.x 移除了 2.x 中的 beforeCreate 和 created 钩子,通过 setup 方法代替。
// vue2.x
export default {
data () {
return {}
},
methods: {
...
},
beforeCreate() {},
created() {},
beforeMount() {},
mounted() {},
beforeUpdate() {},
updated() {},
beforeDestroy() {},
destroyed() {}
}
// vue3.x
import {
onBeforeMount,
onMounted,
onBeforeUpdate,
onUpdated,
onBeforeUnmount,
onUnmounted
} from 'vue'
export default {
setup() {
onBeforeMount(() => {
console.log('component is onBeforeMount')
})
onMounted(() => {
console.log('component is onMounted')
})
onBeforeUpdate(() => {
console.log('component is onBeforeUpdate')
})
onUpdated(() => {
console.log('component is onUpdated')
})
onBeforeUnmount(() => {
console.log('component is onBeforeUnmount')
})
onUnmounted(() => {
console.log('component is onUnmounted')
})
}
}
- 响应式数据对象相关方法:reactive 方法、ref 方法、toRef 等方法,数据的只读属性,计算属性,方法的拆解,watchEffect,useStore 等相关方法不在此一一详解。
四、Fragment 片段、Teleport、Suspense
(1)Fragment 片段
- Vue2.x中,vue template只允许有一个根节点。
- Vue3.0中,vue template支持多个根节点。
// vue2.x
<template>
<div>
<Headers></Headers>
<Main></Main>
<Footer></Footer>
</div>
</template>
// Vue3.0
<template>
<Headers></Headers>
<Main></Main>
<Footer></Footer>
</template>
(2)Teleport
Teleport 是一种能够将我们的模板渲染至指定 DOM 节点,不受父级 style 、v-show 等属性影响,但 data、prop 数据依旧能够共用的技术;类似于 React 的 Portal。
(3)Suspense
<Suspense>
是一个特殊的组件,它将呈现回退内容,而不是对于的组件,直到满足条件为止,这种情况通常是组件 setup 功能中发生的异步操作或者是异步组件中使用。
使用场景:父组件展示的内容包含异步的子组件,异步的子组件需要一定的时间才可以加载并展示,这时就需要一个组件处理一些占位逻辑或者加载异常逻辑。
// vue2.x
<template>
<div>
<div v-if="!loading">
...
</div>
<div v-if="loading">Loading...</div>
</div>
</template>
// vue3.x
<Suspense>
<template >
<Suspended-component />
</template>
<template #fallback>
Loading...
</template>
</Suspense>
五、自定义渲染 API
自定义渲染 API 将 Virtual DOM(虚拟DOM)和平台相关的渲染分离。
通过 createRendererAPI 我们可以自定义 Virtual DOM 渲染到某一平台中时的所有操作,比如新增、修改、删除一个“元素”,我们可以这些方法中替换或修改为我们自定义的逻辑,从而打造一个我们自定义的渲染器。
利用这个 API,在 Vue3.0 中我们可以*方便地去构建 Web(浏览器)平台或非 Web 平台的自定义渲染器。
在 Vue 中是利用 runtime-dom 方法提供的一个上层的抽象层,它帮我们完成了 Virtual DOM 渲染到 Web DOM 中的复杂浏览器接口编程操作。
通过编写自定义渲染器,极大丰富了 Vue 的使用场景。
六、源码优化
(1)使用 monorepo 来管理源码
- Vue.js 2.x 的源码托管在 src 目录,然后依据功能拆分 出了 compiler(模板编译的相关代码)、core(与平台无关的通用运行时代码)、platforms(平台专有代码)、server(服务端渲染的相关代码)、sfc(.vue 单文件解析相关代码)、shared(共享工具代码)等目录。
- Vue.js 3.0,整个源码是通过 monorepo 的方式维护的,根据功能将不同的模块拆分到 packages 目录下面不同的子目录中,每个 package 有各自的 API、类型定义和测试。
(2)使用 Typescript 来开发源码
- Vue.js 2.x 选用 Flow 做类型检查,来避免一些因类型问题导致的错误,但是 Flow 对于一些复杂场景类型的检查,支持得并不好。
- Vue.js 3.0 抛弃了 Flow ,使用 TypeScript 重构了整个项目。TypeScript 提供了更好的类型检查,能支持复杂的类型推导;由于源码就使用 TypeScript 编写,也省去了单独维护 d.ts 文件的麻烦。
Vue3 面试题总结
Vue3.0 源码分析
- Vue.js 3.0 响应式系统的实现原理?
- 响应式是惰性的
- vue3 新增 Composition API
- Vue 3.0 所采用的 Composition Api 与 Vue 2.x使用的 Options Api 有什么区别?
- vue3 新增内置组件 Teleport
- Props 初始化和更新流程改进
- Vue3 Slot内容分发
- Vue 3.0 在编译方面有哪些优化?
- Vue3 依赖注入子孙组件如何共享数据
- Vue3 侦听器实现原理与使用场景
- Vue3 组件实现原理核心源码解读
- Vuex 数据流管理方案
- 原生服务端渲染(SSR)的实现、同构开发
- Nuxt.js 集成式 SSR 框架
vue3.0实战应用
- 说说 Vue2.0 和 Vue3.0 有什么区别
- Vue3 的新特性
- 基于 vite/webpack
- 实现 Vue3 工程化部署
- 掌握 setup 和 10 种响应式系统 API
- 掌握新生命周期函数
- 模板 refs 的使用
- Vue3 中的响应式系统和 dom-diff
需要前端学习笔记、大厂面试题、 Vue3.0 面试题 PDF文档(含答案解析),点击此处免费领取!