写点什么

盘点 Vue3 watch 的一些关键时刻能够大显身手的功能

  • 2024-11-22
    福建
  • 本文字数:3783 字

    阅读完需:约 12 分钟

前言


watch 这个 API 大家应该都不陌生,在 Vue3 版本中给 watch 增加不少有用的功能,比如deep选项支持传入数字pause、resume、stop方法once选项onCleanup函数。这些功能大家平时都不怎么用得上,但是在一些特定的场景中,他们能够起大作用,这篇文章欧阳就来带你盘点一下这些功能。


deep支持传入数字


deep选项大家应该比较熟悉,常见的值为true或者false,表示是否深度监听watch传入的对象。

在 Vue3.5 版本中对deep选项进行了增强,不光支持布尔值,而且还支持传入数字,数字表示需要监听的层数。


比如下面这个例子:


const obj1 = ref({  a: {    b: 1,    c: {      d: 2,      e: {        f: 3,      },    },  },});
watch( obj1, () => { console.log("监听到obj1变化"); }, { deep: 3, });
function changeDeep3Obj() { obj1.value.a.c.d = 20; // 能够触发watch回调}
function changeDeep4Obj() { obj1.value.a.c.e.f = 30; // 不能触发watch回调}
复制代码


在上面的例子watchdeep选项值是 3,表明监听到对象的第 3 层。


changeDeep3Obj函数中就是修改对象的第 3 层的d属性,所以能够触发watch的回调。


changeDeep4Obj函数是修改对象的第 4 层的f属性,所以不能触发watch的回调。


他的实现也很简单,我们来看一下 deep 相关的源码:


function watch(source, cb, options) {  // ...省略  if (cb && deep) {    const depth = deep === true ? Infinity : deep    getter = () => traverse(baseGetter(), depth)  }  // ...省略}
复制代码


这里的depth就表示 watch 监听一个对象的深度。


如果deep选项的值为 true,那么就将depth设置为正无穷Infinity,说明需要监听到对象的最深处。


如果deep选项的值为 false,或者没有传入deep,那么就表明只需要监听对象的最外层。


如果deep选项的值为 number 类型数字,那么就把这个数字赋给depth,表明需要监听到对象的具体某一层。


pause、resume、stop 方法


这三个方法也是 Vue3.5 版本中引入的,通过解构watch函数的返回值就可以直接拿到pauseresumestop这三个方法。


我们来看一下源码,其实很简单:


function watch(source, cb, options) {  // ...省略  watchHandle.pause = effect.pause.bind(effect)  watchHandle.resume = effect.resume.bind(effect)  watchHandle.stop = watchHandle  return watchHandle}
复制代码


watch 返回了一个名为watchHandle的对象,对象上面有pause、resume、stop这三个方法,所以我们可以通过解构watch函数的返回值拿到这三个方法。


pause方法的作用是“暂停”watch 回调的触发,也就是说在暂停期间不管 watch 监听的响应式变量如何改变,他的回调函数都不会触发。


有“暂停”,那么肯定就有“恢复”。


resume方法的作用是恢复 watch 回调的触发,此时会主动执行一次 watch 的回调。后面 watch 监听的响应式变量改变时,他的回调函数也会触发。


来看个 demo,代码如下:


<template>  <button @click="count++">count++</button>  <button @click="runner.pause()">暂停</button>  <button @click="runner.resume()">恢复</button>  <button @click="runner.stop()">停止</button></template>
<script setup lang="ts">import { watch, ref } from "vue";
const count = ref(0);const runner = watch(count, () => { console.log(count.value);});</script>
复制代码


点击“count++”按钮会导致watch回调中的 console 执行。


但是当我们点击了“暂停”按钮后,此时我们再怎么点击“count++”按钮都不会触发watch的回调。


点击恢复按钮后会立即触发一次watch回调的执行,后面点击“count++”按钮也同样会触发watch的回调。


我们来看看pauseresume方法的源码,很简单,代码如下:


class ReactiveEffect {  pause(): void {    this.flags |= EffectFlags.PAUSED  }
resume(): void { if (this.flags & EffectFlags.PAUSED) { this.flags &= ~EffectFlags.PAUSED if (pausedQueueEffects.has(this)) { pausedQueueEffects.delete(this) this.trigger() } } }
trigger(): void { if (this.flags & EffectFlags.PAUSED) { pausedQueueEffects.add(this) } else if (this.scheduler) { this.scheduler() } else { this.runIfDirty() } }}
复制代码


pauseresume方法中通过修改flags属性的值,来切换是不是“暂停状态”。


在执行trigger方法依赖触发时,就会先去读取flags属性判断当前是不是“暂停状态”,如果是那么就不去执行 watch 的回调。


从上面的代码可以看到这三个方法是在ReactiveEffect类上面的,这个ReactiveEffect类是 Vue 的一个底层类,watchwatchEffectwatchPosEffectwatchSyncEffect都是基于这个类实现的,所以他们自然也支持pauseresumestop这三个方法。


最后就是stop方法了,当你确定后面都不再想要触发 watch 的回调了,那么就调用这个stop方法。代码如下:


const watchHandle: WatchHandle = () => {  effect.stop()  if (scope && scope.active) {    remove(scope.effects, effect)  }}
watchHandle.stop = watchHandle
复制代码


响应式变量count收集的订阅者集合中有这个 watch 回调,所以当count的值改变后会触发 watch 回调。这里的stop方法中主要是依靠双向链表将这个 watch 回调从响应式变量count的订阅者集合中给 remove 掉,所以执行 stop 方法后无论count变量的值如何改变,watch 回调也不会再执行了。


once选项


如果你只想让你的 watch 回调只执行一次,那么可以试试这个once选项,这个是在 Vue3.4 版本中新加的。

看个 demo:


<template>  <button @click="count++">count++</button></template>
<script setup lang="ts">import { watch, ref } from "vue";
const count = ref(0);watch( count, () => { console.log("once", count.value); }, { once: true, });</script>
复制代码


由于使用了once选项,所以只有第一次点击“count++”按钮才会触发 watch 的回调。后面再怎么点击按钮都不会触发 watch 回调。


我们来看看once选项的源码,很简单,代码如下:


function watch(source, cb, options) {  const watchHandle: WatchHandle = () => {    effect.stop()    if (scope && scope.active) {      remove(scope.effects, effect)    }  }
if (once && cb) { const _cb = cb cb = (...args) => { _cb(...args) watchHandle() } }
// ...省略 watchHandle.pause = effect.pause.bind(effect) watchHandle.resume = effect.resume.bind(effect) watchHandle.stop = watchHandle return watchHandle}
复制代码


先看中间的代码if (once && cb),这句话的意思是如果once选项的值为 true,并且也传入了 watch 回调。那么就封装一层新的cb回调函数,在新的回调函数中还是会执行用户传入的 watch 回调。然后再去执行一个watchHandle函数,这个watchHandle是不是觉得有点眼熟?


前面讲的stop方法其实就是在执行这个watchHandle,执行完这个watchHandle函数后 watch 就不再监听count变量了,所以后续不管count变量怎么修改,watch 的回调也不会再触发。


onCleanup函数


有的情况我们需要 watch 监听一个变量,然后去发起 http 请求。如果变量改变的很快就会出现第一个请求还没回来,第二个请求就已经发起了。在一些极端情况下还会出现第一个请求的响应比第二个请求的响应还要慢,此时第一个请求的返回值就会覆盖第二个请求的返回值。实际上我们期待最终拿到的是第二个请求的返回值。


这种情况我们就可以使用onCleanup函数,他是作为 watch 回调的第三个参数暴露给我们的。看个例子:


watch(id, async (newId, oldId, onCleanup) => {  const { response, cancel } = myFetch(newId)  // 当 `id` 变化时,`cancel` 将被调用,  // 取消之前的未完成的请求  onCleanup(cancel)  data.value = await response})
复制代码


watch 回调的前两个参数大家都很熟悉:新的 id 值和旧的 id 值。第三个参数就是onCleanup函数,在 watch 回调触发之前调用,所以我们可以使用他来 cancel 掉上一次的请求。


onCleanup函数的注册也很简单,代码如下:


let boundCleanup
boundCleanup = fn => onWatcherCleanup(fn, false, effect)
function watch(source, cb, options) { // ...省略 const job = (immediateFirstRun?: boolean) => { const args = [ newValue, oldValue, boundCleanup, ] cb(...args) oldValue = newValue } // ...省略}
复制代码


执行 watch 回调实际就是在执行这个job函数,在job函数中执行 watch 回调时传入了三个参数。分别是newValueoldValueboundCleanup。前两个参数大家都很熟悉,第三个参数boundCleanup是一个函数:fn => onWatcherCleanup(fn, false, effect)


这个onWatcherCleanup大家熟悉不?这也是 Vue 暴露出来的一个 API,注册一个清理函数,在当前侦听器即将重新运行时执行。


总结


这篇文章盘点了 Vue3 watch 新增的一些新功能:deep选项支持传入数字pause、resume、stop方法once选项onCleanup函数。这些功能大家平时可能用不上,但是还是要知道有这些功能,因为有的情况下这些功能能够派上大用场。


文章转载自:前端欧阳

原文链接:https://www.cnblogs.com/heavenYJJ/p/18560249

体验地址:http://www.jnpfsoft.com/?from=infoq

用户头像

还未添加个人签名 2023-06-19 加入

还未添加个人简介

评论

发布
暂无评论
盘点Vue3 watch的一些关键时刻能够大显身手的功能_JavaScript_快乐非自愿限量之名_InfoQ写作社区