用vue3进行开发也有一段日子了,发现越用越爽,但是就是对ref
、computed
、shallowRef
、customRef
、toRef
使用的时候都需要带上.value,这点就令人很不爽了,其中最烦人的就是ref
因为使用率最高,经常动不动就一个文件到处都是.value,不过好在我们可以响应性语法糖解决这个令人厌烦的问题。
响应性语法糖
由于响应性语法糖还是一个在实验性的功能,需要显示开启才能使用,具体设计在最终定稿前仍可能发生变化。
ref and 响应式语法糖
<template>
<button @click="add">{{ count }}</button>
</template>
<script setup>
let count = $ref(0);
function add() {
count++
console.log(count) // 1
}
</script>
我们可以直接使用$ref()
来代替ref
,但这里的$ref()
其实是一个编译时的宏命令,并不是真实的,它只是Vue编译器的标记,标识 count
变量需要是一个响应式的变量,可以想普通的变量一样赋值、访问,但在编译时都会变成带.value的ref变量。
向上方的代码在编译时就会变成这样:
let count = ref(0);
function add() {
count.value++
console.log(count.value) // 1
}
每一个会返回ref
的响应式API都有一个与之对应的,以$为前缀的宏函数,包括以下API:
-
ref
->$ref
-
computed
->$computed
-
shallowRef
->$shallowRef
-
customRef
->$customRef
-
toRef
->$toRef
在启用响应性语法糖后,这些宏函数都是默认不需要引入可以直接在全局使用的,但如果你想让它更加明显,也可以显示的使用它们
import { $ref } from 'vue/macros'
let count = $ref(0)
通过$()解构
我们通常会在组合函数内返回多个ref,然后解构得到这些 ref,对于这种场景,响应性语法糖提供了一个 $()
宏:
import { useMouse } from '@vueuse/core'
const { x, y } = $(useMouse())
console.log(x, y)
编译时:
import { toRef } from 'vue'
import { useMouse } from '@vueuse/core'
const __temp = useMouse(),
x = toRef(__temp, 'x'),
y = toRef(__temp, 'y')
console.log(x.value, y.value)
如果x
已经是一个ref 那么toRef(__temp, 'x')
则会简单的返回它本身,如果一个被解构的值并不是ref(比如一个函数)也是可以正常的使用的,这个值会被包进一个ref,因此所有的代码都可以正常的执行。
我们可以利用这个特性解决一个pinia令人厌烦的问题,解构时需要使用storeToRefs来解构,因为如果不使用这个方法会时其失去响应性,但是要是每次使用时都需要再写一行
import { storeToRefs } from 'pinia'
这太麻烦了,既然直接解构会失去相应性,那我们就在使用的时候直接带上$(),就可以直接解构并且不会失去响应性。
import { useUserStore } from "../../stores/user.js";
let { userInfo, updataUserInfo } = $(useUserStore());
如果你在自己的pinia中使用的是 r e f 则会需要下面介绍的 ref 则会需要下面介绍的 ref则会需要下面介绍的$() 来进行返回。
保持函数间传递时的响应性
虽然我们使用响应性语法糖可依避免.value,但是它可能会在我们的函数间传递时可能出现响应性丢失的情况。
例如:
export const useUserStore = defineStore('user', ()=>{
// 用户信息
let userInfo = $ref({});
// 更新用户信息
const updataUserInfo = ()=>{
...
};
return {
userInfo,
updataUserInfo
};
})
我们在编写时它是这个样子,但是编译时会变成这个样式:
export const useUserStore = defineStore('user', ()=>{
// 用户信息
let userInfo = $ref({});
// 更新用户信息
const updataUserInfo = ()=>{
...
};
return {
userInfo: userInfo.value,
updataUserInfo
};
})
这里的 userInfo
是以对象的形式被返回也就意味着其失去了响应性,要想使用真正的ref,而不是被编译时带.value的变量,可以在返回时使用$$()来包装。
return {
userInfo: userInfo,
updataUserInfo
};
我们可以看到,$$()
的效果就像是一个转义标识:$$()
中的响应式变量不会追加上 .value
。
TypeScript 集成
Vue 为这些宏函数都提供了类型声明 (全局可用),因此类型推导都会符合预期。它与标准的 TypeScript 语义没有不兼容之处,因此它的语法可以与所有现有的工具兼容。
这也意味着这些宏函数在任何 JS / TS 文件中都是合法的,不是仅能在 Vue SFC 中使用。
因为这些宏函数都是全局可用的,它们的类型需要被显式地引用 (例如,在 env.d.ts
文件中):
ts
/// <reference types="vue/macros-global" />
若你是从 vue/macros
中显式引入宏函数时,则不需要像这样全局声明。
显式启用
重点来了,敲黑板!!!
说了这么多的优点、用法,咱赶紧用上吧,不仅可以带来便利还可以附带点13属性。
由于是在实验阶段,所以目前还是默认关闭的,我们需要显示的启用它。此外,以下列出的所有配置都需要 vue@^3.2.25
。
Vite
- 需要
@vitejs/plugin-vue@>=2.0.0
- 应用于 SFC 和 js(x)/ts(x) 文件。在执行转换之前,会对文件进行快速的使用检查,因此不使用宏的文件应该不会有性能损失。
- 注意
reactivityTransform
现在是一个插件的顶层选项,而不再是位于script.refSugar
之中了,因为它不仅仅只对 SFC 起效。
// vite.config.js
export default {
plugins: [
vue({
reactivityTransform: true
})
]
}
vue-cli
- 目前仅对 SFC 起效
- 需要
vue-loader@>=17.0.0
// vue.config.js
module.exports = {
chainWebpack: (config) => {
config.module
.rule('vue')
.use('vue-loader')
.tap((options) => {
return {
...options,
reactivityTransform: true
}
})
}
}
仅用 webpack
+ vue-loader
- 目前仅对 SFC 起效
- 需要
vue-loader@>=17.0.0
// webpack.config.js
module.exports = {
module: {
rules: [
{
test: /.vue$/,
loader: 'vue-loader',
options: {
reactivityTransform: true
}
}
]
}
}
今日的课程就到这里啦,感谢您的耐心阅读,新的一年平安喜乐。
最后附上我的那句经典话语