今天在Vue3官方文档上看了下 watchEffect 的介绍。以前用的时候都用得比较简单,所以以为这 api 也没啥值得特别掌握的地方,今天看了文档才发现,这个 api 并没有想象中那么简单~
特别是看到清除副作用的时候比较懵逼,最后结合CSDN上一些大佬的博客解释才慢慢明白这其中的精髓。
正好昨天刚写了一个关于Vue2如何实现防抖函数的文章,那么接下来,我就再次用一个防抖函数的实现过程来给大家分析一下这个api以及可能遇到的坑。

我模拟输入框搜索功能来演示,直接上代码:

<template>
  <el-input
    v-model="input"
    placeholder="Please input"
  />
</template>

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

// 搜素关键字
const input = ref('')

// 定义一个 timer 来保存 setTimeout
let timer: any = null

// 定义一个返回 setTimeout 的方法
// ???(为什么要定义返回setTimeout的方法)
const getData = (wordKey: string) => {
  return setTimeout(() => {
    // ...Request Data
    console.log(wordKey)
  }, 3000)
}

// 开始监听 input 的 value 变化
// ???(如果把 getData 放在 watchEffect 之后,代码可以正常执行吗)
watchEffect(onInvalidate => {
  timer = getData(input.value)
  onInvalidate(() => {
    if (timer) {
      clearTimeout(timer)
    }
  })
})
</script>

在这段代码中,一共有两个问题,也就是我在写这段代码时遇到的两个坑需要给大家说明一下:

1.为什么要定义返回setTimeout的方法?
我之前写这个setTimeout的时候是这样写的:

const getData = (wordKey: string) => {
	// ...Request Data
}
watchEffect(onInvalidate => {
  timer = setTimeout(() => getData(input.value), 3000)
  onInvalidate(() => {
    if (timer) {
      clearTimeout(timer)
    }
  })
})

这样写过后,我发现当 input 的 value 发生改变时,watchEffect
并不能监听到它的变化。我查阅了一些资料,找到一些线索,猜测 watchEffect
在收集依赖时,只会收集自身上下文作用域里的响应式数据。当然,这只是我的猜测,自己还没去证实过,之后会去源码中找线索继续求证~
总之,watchEffect是收集不到setTimeout内部函数的依赖的,所以,当时我将setTimeout提到了getData里,通过在watchEffect自身上下文作用域里将input.value传入到getData函数中来实现监听。如果各位大佬清除watchEffect收集依赖的方式,请在评论区或者私信中多多指教呀~

2.如果把 getData 放在 watchEffect 之后,代码可以正常执行吗?
我之前写这段代码的时候是这样写的:

watchEffect(onInvalidate => {
  timer = getData(input.value)
  onInvalidate(() => {
    if (timer) {
      clearTimeout(timer)
    }
  })
})
const getData = (wordKey: string) => {
  return setTimeout(() => {
    // ...Request Data
    console.log(wordKey)
  }, 3000)
}

大家可以看到,我把 getData 放到了 watchEffect 之后,运行后我发现报了这样一个错:
在这里插入图片描述
大家可以看到是一个空指针的报错。后来我发现了是 getData 找不到,why?我不是定义了吗,Vue bug?!
后来去仔细看了下官方文档发现这样一段话:
在这里插入图片描述
原来是立即执行函数,这好像跟Promise类似哈~
这样就能解释通了,在他执行的时候,我们的 getData 根本还没有注册到组件实例上,当然报空指针啦。
不过我在官方文档后面又看到了可以通过配置 flush 选项来控制watchEffect 内部监听函数的执行时机:
在这里插入图片描述
于是我试一下将代码这样改写:

<template>
  <el-input
    v-model="input"
    placeholder="Please input"
  />
</template>

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

// 搜素关键字
const input = ref('')

// 定义一个 timer 来保存 setTimeout
let timer: any = null

// 开始监听 input 的 value 变化
watchPostEffect(onInvalidate => {
  timer = getData(input.value)
  onInvalidate(() => {
    if (timer) {
      clearTimeout(timer)
    }
  })
})

// 定义一个返回 setTimeout 的方法
const getData = (wordKey: string) => {
  return setTimeout(() => {
    // ...Request Data
    console.log(wordKey)
  }, 3000)
}
</script>

<style>

</style>

也就是利用 watchPostEffect 去改变内部监听函数的执行时机,发现一样可以实现咱们的需求~

Logo

前往低代码交流专区

更多推荐