◆

Vue3 ref 系列 API:unref、toRef、customRef、shallowRef

2023-02-02 01:370

ref

接受一个内部值并返回一个响应式且可变的 ref 对象。ref 对象具有指向内部值的单个 property.value。

  • ref 内部: 通过给 value 属性添加 getter/setter 来实现对数据的劫持
  • let count = 0 此时的数据并不是响应式的数据(响应式数据:数据变化,页面跟着渲染变化)
  • template 模版中是不需要使用 .value 属性的写法

示例:

const count = ref(0);
console.log(count.value); // 0
count.value++;
console.log(count.value); // 1

如果将对象分配为 ref 值,则通过 reactive 方法使该对象具有高度的响应式。 有时我们可能需要为 ref 的内部值指定复杂类型。想要简洁地做到这一点,我们可以在调用 ref 覆盖默认推断时传递一个泛型参数:

const foo = (ref < string) | (number > 'foo'); // foo 的类型:Ref<string | number>
foo.value = 123; // ok!

以下示例中 refValue 的值 1.5 秒后会改变,而 temp 的值不会

tip

使用 ref 去定义非基础数据类型的变量时, 和 reactive 定义效果一致 只不过 ref 中定义的数据需要 .value 这种写法

<template>
  <div>
    <p>这是 refValue 的值:{{ refValue }}</p>
    <p>这是普通变量 temp 的值:{{ temp }}</p>

    <p>这是 refObj 的值:{{ refObj }}</p>
    <p>这是 refArray 的值:{{ refArray }}</p>
  </div>
</template>

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

const refValue = ref('refValue');
let temp = 'tempValue';

let refObj = ref({
  a: 1,
  b: 2,
});

let refArray = ref([1, 2, 3, 4]);

setTimeout(() => {
  refValue.value = '更新的 refValue';
  refObj.value = { a: 4444, b: 666 };
  (refArray as any).value = [{ a: 4444, b: 666 }];
  temp = '更新的 tempValue';
}, 1000);

setTimeout(() => {
  (refObj as any).value.a = '更新';
}, 2000);

setTimeout(() => {
  (refObj as any).value.push = { a: 33, b: 666 };
  (refArray as any).value.push({ c: 4444, d: 666 });
}, 3000);
</script>

unref

如果参数是一个 ref,则返回内部值,否则返回参数本身。这是 val = isRef(val) ? val.value : val 的语法糖函数。

<template>
  <div>noRef:{{ noRef }}</div>
  <div>refTest:{{ refTest }}</div>
  <div>unrefTest:{{ unrefTest }}</div>
</template>

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

const refTest = ref('refTest');
let unrefTest = unref(refTest);
let noRef = 'noRef';

setTimeout(() => {
  refTest.value = '这时更改了 ref';
  unrefTest = '这时更改了 ref';
}, 1500);
</script>

上述代码 refTest unrefTest noRef 三个变量都会渲染在 template 上,但在 1.5s 后只有 refTest 这个变量改变了,因为其他两个都不是响应式变量

toRef

可以用来为源响应式对象上的某个 property 新创建一个 ref。然后,ref 可以被传递,它会保持对其源 property 的响应式连接。

tip

由于 props 是响应式的,你不能使用 ES6 解构,它会消除 prop 的响应性。 所以从 props.toRefValues 解构后拿的值,不会响应更新

示例

父组件代码

<template>
  <div>父组件中的值: {{ toRefValues.toRefValueOne }}</div>
  <div>父组件中的值: {{ toRefValues.toRefValueTwo }}</div>
  <hr />
  <ChildTest :toRefValues="toRefValues"></ChildTest>
</template>

<script setup>
import ChildTest from './childTest.vue';
import { reactive } from 'vue';

const toRefValues = reactive({
  toRefValueOne: 'toRefOne',
  toRefValueTwo: 'toRefTwo',
});

setTimeout(() => {
  toRefValues.toRefValueOne = 'toRefValueOne 值更改了';
  toRefValues.toRefValueTwo = 'toRefValueTwo 值更改了';
}, 1500);
</script>

子组件代码

<template>
  <div>直接从 props 拿值: {{ toRefValues.toRefValueOne }}</div>
  <div>直接从 props 拿值: {{ toRefValues.toRefValueTwo }}</div>
  <div>从 props.toRefValues 解构后拿的值: {{ toRefValueOne }}</div>
  <div>从 props.toRefValues 解构后拿的值: {{ toRefValueTwo }}</div>
  <div>通过 toRef 加工后拿的值: {{ toRefValueOnes }}</div>
  <div>通过 toRef 加工后拿的值: {{ toRefValueTwos }}</div>
</template>

<script setup>
import { propTypes } from '../../../utils/propTypes';
import { toRef } from 'vue';

const props = defineProps({
  toRefValues: propTypes.object,
});

const { toRefValues } = props;

const { toRefValueOne, toRefValueTwo } = toRefValues;

const toRefValueOnes = toRef(toRefValues, 'toRefValueOne');
const toRefValueTwos = toRef(toRefValues, 'toRefValueTwo');

setTimeout(() => {
  toRefValueOnes.value = '在子组件更新了';
}, 4000);
</script>

上述代码 直接从 props 拿值 与 通过 toRef 加工后拿的值都会响应式的变化,但从 props.toRefValues 解构后拿的值 不会响应式变化。 同时在子组件对父组件的某个属性进行 toRef,toRef 后的值改变同时也会影响父组件对应的属性值改变

toRefs

将响应式对象转换为普通对象,其中结果对象的每个 property 都是指向原始对象相应 property 的 ref。

示例

父组件

<template>
  <div>父组件中的值: {{ toRefsValues.toRefsValueOne }}</div>
  <div>父组件中的值: {{ toRefsValues.toRefsValueTwo }}</div>
  <hr />
  <ToRefsTest :toRefsValues="toRefsValues"></ToRefsTest>
</template>

<script setup>
import ToRefsTest from './toRefsTest.vue';
import { reactive } from 'vue';

const toRefsValues = reactive({
  toRefsValueOne: 'toRefsOne',
  toRefsValueTwo: 'toRefsTwo',
});

setTimeout(() => {
  toRefsValues.toRefsValueOne = 'toRefsValueOne 值更改了';
  toRefsValues.toRefsValueTwo = 'toRefsValueTwo 值更改了';
}, 1500);
</script>

子组件

<template>
  <div>直接从 props 拿值: {{ toRefsValues.toRefsValueOne }}</div>
  <div>直接从 props 拿值: {{ toRefsValues.toRefsValueTwo }}</div>
  <div>从 props.toRefsValues 解构后拿的值: {{ toRefsValueOne }}</div>
  <div>从 props.toRefsValues 解构后拿的值: {{ toRefsValueTwo }}</div>
  <div>通过 toRefs 加工后拿的值: {{ toRefsValuesChild.toRefsValueOne }}</div>
  <div>通过 toRefs 加工后拿的值: {{ toRefsValuesChild.toRefsValueTwo }}</div>
</template>

<script setup>
import { propTypes } from '../../../utils/propTypes';
import { toRefs } from 'vue';

const props = defineProps({
  toRefsValues: propTypes.object,
});

const { toRefsValues } = props;

const { toRefsValueOne, toRefsValueTwo } = toRefsValues;

const toRefsValuesChild = toRefs(toRefsValues);

setTimeout(() => {
  toRefsValuesChild.toRefsValueOne.value = '在子组件更新了';
}, 4000);
</script>

上述代码 直接从 props 拿值 与 通过 toRef 加工后拿的值都会响应式的变化,但从 props.toRefValues 解构后拿的值 不会响应式变化。 同时在子组件对父组件传递的对象进行 toRefs,toRefs 后的对象改变同时也会影响父组件对应的属性值改变。

isRef

检查值是否为一个 ref 对象。

<template>
  <div>refTest:{{ refTest }}</div>
  <div>unrefTest:{{ unrefTest }}</div>
</template>

<script setup>
import { isRef, ref, unref } from 'vue';

const refTest = ref('refTest');
const unrefTest = unref(refTest);
// true
console.log(isRef(refTest));
// false
console.log(isRef(unrefTest));
</script>

customRef

创建一个自定义的 ref,并对其依赖项跟踪和更新触发进行显式控制。它需要一个工厂函数,该函数接收 track 和 trigger 函数作为参数,并且应该返回一个带有 get 和 set 的对象。 使用自定义 ref 通过 v-model 实现 debounce 的示例:

<input v-model="text" />;

function useDebouncedRef(value, delay = 200) {
  let timeout;
  return customRef((track, trigger) => {
    return {
      get() {
        track();
        return value;
      },
      set(newValue) {
        clearTimeout(timeout);
        timeout = setTimeout(() => {
          value = newValue;
          trigger();
        }, delay);
      },
    };
  });
}

export default {
  setup() {
    return {
      text: useDebouncedRef('hello'),
    };
  },
};

shallowRef && triggerRef

shallowRef:创建一个跟踪自身 .value 变化的 ref,但不会使其值也变成响应式的。 triggerRef:手动执行与 shallowRef 关联的任何作用

tip

shallowRef 可直接修改一层的对象,直接更改 .value 即可, 但如果修改具有深层次的对象属性时,如果想要实现响应式则需要使用 triggerRef 去触发

什么时候用 如果有一个对象数据, 后面会产生新的对象来替换使用 shallowRef

<template>
  <p>{{ state }}</p>
  <button @click="updateFirst">直接更改第一层 value 的数据</button>
  <br />
  <br />
  <button @click="updateDeep">更改深层次的数据,不会直接触发</button>
  <button @click="updateDeeps">点击再出发</button>
  <br />
  <br />
  <button @click="updateDeepQuick">更改深层次的数据并直接触发</button>
</template>
<script lang="ts" setup>
import { defineComponent, shallowRef, triggerRef } from 'vue';
//shallowRef生成非递归响应数据,只监听第一层数据的变化
let state = shallowRef({
  gf: {
    f: {
      s: {
        d: 4,
      },
      c: 3,
    },
    b: 2,
  },
  a: 1,
});

function updateFirst() {
  (state as any).value = {
    a: 3,
  };
}

function updateDeep() {
  state.value.a = 666;
  state.value.gf.b = 666;
  state.value.gf.f.c = 666;
  state.value.gf.f.s.d = 666;
}

function updateDeeps() {
  triggerRef(state);
}

function updateDeepQuick() {
  state.value.a = 777;
  state.value.gf.b = 888;
  state.value.gf.f.c = 999;
  state.value.gf.f.s.d = 0;

  triggerRef(state);
}
</script>
💬留言板0

还没有留言,来发表第一条吧~

PAGE LOAD / LIVEVue3 ref 系列 API:unref、toRef、customRef、shallowRef加载中