Vue3 学习与对比
zKing 2022-06-21 Vue.js
由于已经熟悉了解 Vue2.x,所以这次需要通过阅读文档来寻找 Vue3 语法上的一些不同点加以记录,完成后再和迁移文档进行查漏补缺,当前 Vue3 版本为 3.2.27
目前已知没有什么改变的点
- v-bind 和 v-on 还是和 Vue2.x 的基本用法一致
- v-model 语法糖还存在,但是默认名称更改了,且多了修饰符增强,详细看这里
- v-if 和 v-for 这两个渲染指令也没有变化,但是优先级发生了改变
- Vue2.x 中 v-for 更优先
- Vue3.x 中 v-if 更优先
- 还是可以使用
:is
来绑定动态组件,但是一些行为发生了变化
# 组合式 API
Vue2.x 比较出名的就是使用 SFC 模式进行开发,其中关于 script 模块,以前是使用选项式 API,也就是源码中常见的 options 处理。这次 Vue3 除了原有的选项式写法外,还支持了组合式 API 写法。需要搭配使用 <script setup>
的写法
Vue3 中,选项式 API 也是用组合式 API 实现的!
相比 Vue2.x 只能 import Vue from 'vue'
, Vue3 由于使用组合式 API,所以提供了不少新的方法来导出
# reactive 和 ref
- 这两个方法都用于双向绑定,但是
reactive
仅适用于对象(包括数组和 Map,Set 等内置类型),ref
则可以采用任何值类型并创建一个对象,真实的值存储在 'value' 字段中,但在模板中会自动获取到 value 中的数据
<script setup>
import { ref, reactive, onMounted } from "vue";
defineProps({
msg: String
});
const count = ref(0);
// 这里也可以使用 ref 来双向绑定
const countMap = reactive({
num: 1
});
onMounted(() => {
console.log("CompositionApiSample mounted", count.value);
// 1. 用 reactive 来绑定的话,会把 countMap 打印一个 proxy 对象, 并且能获取 countMap 的值
// 2. 用 ref 来绑定的话,会把 countMap 打印出一个 object, 且需要通过 value 字段来获取值 countMap.value.num
console.log("countMap", countMap.num, countMap);
});
</script>
<template>
<h1>{{ msg }}</h1>
<button type="button" @click="count++">count is: {{ count }}</button>
<button type="button" @click="countMap.num++">
countMap.num is: {{ countMap.num }}
</button>
</template>
- 另外还有
toRef
和toRefs
函数方法- toRef 可用于为响应式对象上的 property 创建 ref。这样创建的 ref 与其源 property 保持同步:改变源 property 将更新 ref,反之亦然。
- toRefs 将一个响应式对象转换为一个普通对象,这个普通对象的每个 property 都是指向源对象相应 property 的 ref。每个单独的 ref 都是使用 toRef() 创建的。
# computed
组合式 API 也能导出一个 computed
钩子来“计算属性”,比较恶心的是,每次都需要使用 .value
的方式来访问 ref
中的值,不然就得改用 reactive
来进行绑定,github 上也有相关的 discussion 来讨论怎么优化 .value
的问题,后续再跟进看看吧
# watch
- 一般情况下,watch 函数第一个入参为“双向绑定的变量”, 第二个参数为一个回调函数,和 Vue2.x 一样,回调函数有
(newCount, oldCount)
两个回调入参 - watch 的第一个参数可以是不同形式的“来源”:它可以是一个 ref (包括计算属性)、一个响应式对象、一个 getter 函数、或多个来源组成的数组:
- 在此之外,watch 函数能扩展很多,且有很多限制,具体可以看文档的示例,这里简单记录一下
- watch 函数的第一个参数可以是一个回调函数,用作 getter 计算,第二个入参会拿到这个 getter 值
- 当监听一个
reactive
绑定的对象属性值时,需要使用 getter 回调函数,而不是直接监听该对象的属性值 - watch 函数还有第三个入参
{ deep:true }
进行深度监听(耗性能)
- Vue3 在 optionApi 中留下
immedicate
属性来立即执行,但在 compositionApi 中则使用另外一个watchEffect
方法来替代,它会立即执行一遍回调函数,如果这时函数产生了副作用,Vue 会自动追踪副作用的依赖关系,自动分析出响应源。 - 如果想在侦听器回调中能访问被 Vue 更新之后的 DOM,你需要指明 flush: 'post' 选项,或者使用
watchPostEffect
方法 - 一个关键点是,侦听器必须用同步语句创建:如果用异步回调创建一个侦听器,那么它不会绑定到当前组件上,你必须手动停止它,以防内存泄漏
# 组件注册
- 在使用 optionApi 时,需要在
components
属性中传入组件进行注册 - 但使用 compositionApi 则不需要,只要
import
到 SFC 文件中,就会自动注册到当组件中 - 根组件实例化不使用
new Vue
,而是使用createApp
的方法来创建 - 另外还是可以继续使用
use
和component
方法进行注册
# Props 和 Emits
- compositionApi 中使用
defineProps
方法来定义Props
,入参和 optionApi 中的 props 一致,但值得注意的是,如果要在 script 中访问Props
相关的值,需要使用defineProps
函数的返回值来获取 - compositionApi 中使用
defineEmits
方法来定义Emits
,入参是一个字符串数组(事件名称数组),返回一个 emit 函数。emit 函数的第一个入参是字符串(事件名称), 第二个是回调参数,和 Vue2.x 中的$emit 用法一致
。【注意,即使是用 optionApi,也需要用 emits 字段来预先定义好相关事件名称】 - 另外,
defineProps
和defineEmits
在<script setup>
中是直接可用的 可以不用手动import
- 在 Typescript 中可以使用类型声明的方式进行使用,但需要用
withDefaults
来为 Props 设置默认值
<script setup>
const props = defineProps({
msg: {
type: String,
default: "test"
}
});
const emit = defineEmits(["response"]);
emit("response", "It is CompositionApiSample");
</script>
# 其他
<script setup>
是 Vue3 的关键性变更,并且可以使用顶层 await- 在
<script setup>
使用 slots 和 attrs 的情况应该是很罕见的,因为可以在模板中通过 $slots 和 $attrs 来访问它们。在你的确需要使用它们的罕见场景中,可以分别用 useSlots 和 useAttrs 两个辅助函数 - 在 optionApi 中也可以使用
setup
属性做一些处理,会在beforCreate
生命周期前执行。所以,其实<script setup>
和 普通的script
可以一起使用 - compositionApi 模式的组件实例默认是关闭,无法被父级组件直接使用,所以需要使用
defineExpose
方法来暴露数据 <script setup>
不能和 src attribute 一起使用。
# 生命周期
生命周期有两个钩子名称变化了,但是大致的使用场景没有改变
- beforDestroy => beforeUnmount
- destroyed => unmounted
在组合式 API 中,使用 on
来使用生命周期,传入一个回调函数
# CSS v-bind
可以通过双向绑定的方式,直接影响到 CSS 的属性值变化了!这个太赞了
# Teleport 组件
Vue3 新增了该内置组件 Teleport(传送门),可以让组件渲染在某个固定 Dom 节点上,对于模态框之类的组件特别好用
# Suspense 组件
需要注意的是,Suspense 是一个试验性的新特性,生产环境暂不能投入使用
- 该组件包含两个插槽,它们都只接收一个直接子节点。
default
插槽里的节点会尽可能展示出来。如果不能,则展示fallback
插槽里的节点。 - 触发
fallback
的方式是让后代组件从 setup 函数中返回一个 Promise。通常这是通过 async 实现的,而不是显式地返回一个 Promise:
# Fragments
在 Vue2.x 中,一个 SFC 组件只能有一个根节点,但在 Vue3.x 中可以有多个了
# 其他一些改动
- Vue 组件实例上的
$on, $off, $once
等方法被移除了,所以没法再用 Vue 作为 eventBus - 全局函数
set, delete
,以及实例方法$set $delete
等方法也被移除了 - $destroy 实例方法也被移除了
- $children 实例属性也被移除了
v-on.native
的方式被移除了$scopedSlots
属性被删除,所有插槽都通过$slots
函数公开$listeners
已被删除/合并到$attrs
- optionApi 中的 data 属性统一改成使用函数进行数据返回了
- SFC 文件中,
<template>
不能继续使用functional
属性 - Vue.extend 已移除,可以使用 defineComponent 或 extends 选项进行替代【官方认为使用 CompositionApi 进行组合比 extend 和 mixins 更好】
- config.keyCodes 已移除,不能再使用
v-on:keyup.[keyCode]
的方式