Published on

理解 Vue3 的各类 ref: toRef, toRefs, isRef, unref等

Authors
  • avatar
    Name
    Deng Hua
    Twitter

在Vue3中,有很多与响应性相关的函数,例如toRef、toRefs、isRef、unref等,合理使用这些函数可以在实际开发中大大提高效率。

目录

ref

核心API之一,接受一个值并返回一个响应对象。我们可以通过 .value 访问或修改这个值。在模板中,我们可以省略 .value ,例如下面的代码中,当点击按钮时,页面上的计数会响应变化。

<template>
    <div>
        {{ count }}
        <button @click="addCount">+1</button>
    </div>
</template>

<script lang='ts' setup>
import { ref } from "vue"
const count = ref(1)
const addCount = () => {
    count.value++
}
</script>

toRef

toRef 可以根据响应式对象中的属性来创建响应式引用。同时,这个 ref 与原始对象中的属性同步。

如果原始对象属性的值发生变化,ref也会相应变化,反之亦然。它接受两个参数,一个是对应的响应式对象,另一个是属性值,如下面的代码。

<template>
    <div>
        {{ count.a }}
        {{ a }}
        <button @click="addCount">+1</button>
    </div>
</template>

<script lang='ts' setup>
import { ref, toRef } from "vue";

const count = ref({
    a: 1,
    b: 2
})
const a = toRef(count.value, 'a')

const addCount = () => {
    a.value++
}
</script>

当点击按钮修改a的值时,count中的a也会改变。当然,这里的count也可以使用reactive代替。

toRefs

toRefs 可以将响应式对象转换为普通对象,而这个普通对象的每个属性都是一个响应式 ref

<template>
    <div>
        {{ count.a }}
        {{ countAsRefs.a }}
        <button @click="addCount">+1</button>
    </div>
</template>

<script lang='ts' setup>
import { reactive, toRefs } from "vue"
const count = reactive({
    a: 1,
    b: 2
})
const countAsRefs = toRefs(count)
const addCount = () => {
    countAsRefs.a.value++
}

</script>

此时代码中 countAsRefs 的类型为

{
  a: Ref<number>,
  b: Ref<number>
}

它的属性 ab 是响应式ref对象。同样,它们也与原始count对象的属性同步。

根据它的特点,我们通常用它来解构一个响应式对象,同时不会失去其响应性。

import { reactive, toRefs } from "vue";
const count = reactive({
  a: 1,
  b: 2,
});
const { a, b } = toRefs(count);

此时, ab 都是响应式 ref 对象,并且与原始对象 countab 属性同步。

isRef

顾名思义, isRef 用于确定值是否为 ref 对象

注意:它无法确定该值是否是响应性的(isReactive 可用于此目的)。

import { reactive, isRef, ref } from "vue";
const count = ref(1);
const testObj = reactive({
  a: 1,
});
console.log(isRef(count)); //true
console.log(isRef(testObj)); //false

isRef返回值也是一个类型判定 (type predicate),这意味着 isRef 可以被用作类型守卫:

let foo: unknown
if (isRef(foo)) {
  // foo 的类型被收窄为了 Ref<unknown>
  foo.value
}

unref()

事实上,它是一种语法糖。

val = isRef(val) ? val.value : val;

如果它是 ref ,它将返回其内部值,否则,它将返回自身。

这种语法糖表明它可以删除响应性对象的响应性应用。例如,如果我们只想获得一个响应式值但不希望它是响应性的,我们可以使用它来取消引用。

例如:

<template>
    <div>
        {{ unRefAsCount }}
        {{ count }}
        <button @click="addCount">+1</button>
    </div>
</template>

<script lang='ts' setup>
import { unref, ref } from "vue"
const count = ref(1)
let unRefAsCount = unref(count)
const addCount = () => {
    count.value++
}
</script>

unRefAsCount现在没有响应性。

shallowRef

从字面意思来看,我们可以看出它是一个ref()的浅引用。

什么是浅引用?与 ref() 的区别在于,只有 .value 是响应式的,而更深层次的属性不是响应性的。

<template>
    <div>
        {{ shallowObj.a }}
        <button @click="addCount"> +1</button>
    </div>
</template>

<script lang='ts' setup>
import { shallowRef } from "vue"

const shallowObj = shallowRef({
    a: 1
})
const addCount = () => {
  // 不会触发页面更新
  shallowObj.value.a++
}
</script>

但是,如果我们更改 addCount 来修改整个 .value ,它将触发响应性更新。

const addCount = () => {
  let temp = shallowObj.value.a;
  temp++;
  shallowObj.value = {
    a: temp,
  };
};

triggerRef

它允许 shallowRef 在其深层属性发生变化时强制触发更改。例如,在上面的代码示例中添加 triggerRef 就无法触发反应性。

<template>
    <div>
        {{ shallowObj.a }}
        <button @click="addCount"> +1</button>
    </div>
</template>

<script lang='ts' setup>
import { shallowRef, triggerRef } from "vue"

const shallowObj = shallowRef({
    a: 1
})

const addCount = () => {
    shallowObj.value.a++
    // 添加“triggerRef”以强制更改。
    triggerRef(shallowObj)
}
</script>

customRef

顾名思义,它是一个自定义的ref。我们可以通过 customRef 显式跟踪某个值的反应变化。

它接受一个函数,该函数以 tracktrigger 作为参数,并返回一个具有 getset 方法的对象。

例如,封装一个自定义响应性对象myRef并对其进行控制,使其仅在小于 4 时触发响应性。

<template>
  <div>
    {{ count }}
    <button @click="addCount"> +1</button>
  </div>
</template>

<script lang='ts' setup>
import { customRef } from "vue"
const myRef = (value: number) => {
  const customValue = customRef((track, trigger) => {
    return {
      get() {
        track() // 告诉Vue需要跟踪后续内容的变化
        return value;
      },
      set(newValue) {
        console.log(newValue); // myRef.value = xxx
        // 添加“trigger”会触发响应式更新,这里仅在值小于4时触发。
        if (value < 4) trigger()
        value = newValue
      }
    }
  })
  return customValue
}

const count = myRef(0)
const addCount = () => {
  count.value++
}
</script>

count 大于4时,它失去响应性。

End.