你不知道的 vue3 watch 新特性

220 阅读1分钟

学吧 太深了...🐶

最近在看 vue3 代码时发现 watch 新增了一个参数可用于解决实际项目中的一些问题,记录一下使用方法。

type WatchCallback<T> = (
  value: T,
  oldValue: T,
  onCleanup: (cleanupFn: () => void) => void
) => void

vue3 watch 新增第三个参数 onCleanuponCleanup 是一个函数,接收一个方法作为参数,在下一次 watch 的回调执行前执行传入的回调。

<template>
    <el-button @click="count++">
        count++
    </el-button>
    {{ count }}
</template>
<script setup lang="ts">
import { ref, watch } from 'vue';
const count = ref(0);

watch(() => count.value, (value, oldValue, onCleanup) => {
    console.log('进入 watch');
    onCleanup(() => {
        console.log('执行了 onCleanup');
    });
});
点击输出
第一次进入 watch
第二次执行了 onCleanup;进入了watch

使用场景-竞态问题

比如说有一个搜索按钮用于获取数据,连续点击两次按钮发起两个请求 AB,此时我们预期是要得到请求 B 返回的数据,假设极端情况下请求 A 耗时大于请求 B 耗时,最终请求 A 返回的数据会覆盖请求 B 返回的数据,造成非预期的结果。

竞态1.png

<template>
    <el-button @click="count++">
        count++
    </el-button>
</template>
<script setup lang="ts">
import { ref, watch } from 'vue';
const count = ref(0);
const res = ref();

watch(() => count.value, async (value, oldValue, onCleanup) => {
    const data = await getData();
    res.value = data;
});

await function getData() {
  // 异步方法获取数据
}
</script>

可以通过 onCleanup 解决上述竞态问题

<template>
    <el-button @click="count++">
        count++
    </el-button>
</template>
<script setup lang="ts">
import { ref, watch } from 'vue';
const count = ref(0);
const res = ref();

watch(() => count.value, async (value, oldValue, onCleanup) => {
    // 新增 flag 判断是否重新赋值
    let flag = true;
    onCleanup(() => {
        flag = false;
    });
    const data = await getData();
    flag && (res.value = data);
});

await function getData() {
  // 异步方法获取数据
}
</script>

第二次执行 watch 回调时会首先执行 onCleanup 中的回调,把上一轮的 flag 置为 false,这样就使得无论上一次请求何时返回都不会再重新赋值以覆盖新数据。