2023 该放弃vue3中令人厌烦的.value了

时间:2023-01-06 08:51:41

用vue3进行开发也有一段日子了,发现越用越爽,但是就是对refcomputedshallowRefcustomReftoRef使用的时候都需要带上.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
        }
      }
    ]
  }
}

今日的课程就到这里啦,感谢您的耐心阅读,新的一年平安喜乐。

最后附上我的那句经典话语

天才无非是长久的忍耐,努力吧!