盘点Vue3 watch的一些关键时刻能够大显身手的功能
前言
watch这个API大家应该都不陌生,在Vue3版本中给watch增加不少有用的功能,比如deep选项支持传入数字、pause、resume、stop方法、once选项、onCleanup函数。这些功能大家平时都不怎么用得上,但是在一些特定的场景中,他们能够起大作用,这篇文章欧阳就来带你盘点一下这些功能。
关注公众号:【前端欧阳】,给自己一个进阶vue的机会
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回调
}
在上面的例子watch的deep选项值是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函数的返回值就可以直接拿到pause、resume、stop这三个方法。
我们来看一下源码,其实很简单:
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的回调。
我们来看看pause和resume方法的源码,很简单,代码如下:
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()
}
}
}
在pause、resume方法中通过修改flags属性的值,来切换是不是“暂停状态”。
在执行trigger方法依赖触发时,就会先去读取flags属性判断当前是不是“暂停状态”,如果是那么就不去执行watch的回调。
从上面的代码可以看到这三个方法是在ReactiveEffect类上面的,这个ReactiveEffect类是Vue的一个底层类,watch、watchEffect、watchPosEffect、watchSyncEffect都是基于这个类实现的,所以他们自然也支持pause、resume、stop这三个方法。
最后就是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回调也不会再执行了。(PS:如果你看不懂这段话,建议你去看看我的上一篇 Vue3.5双向链表文章,看完后你就懂了)
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回调时传入了三个参数。分别是newValue、oldValue、boundCleanup。前两个参数大家都很熟悉,第三个参数boundCleanup是一个函数:fn => onWatcherCleanup(fn, false, effect)。
这个onWatcherCleanup大家熟悉不?这也是Vue暴露出来的一个API,注册一个清理函数,在当前侦听器即将重新运行时执行。关于onWatcherCleanup之前欧阳写过一篇文章专门讲了如何使用: 使用Vue3.5的onWatcherCleanup封装自动cancel的fetch函数
总结
这篇文章盘点了Vue3 watch新增的一些新功能:deep选项支持传入数字、pause、resume、stop方法、once选项、onCleanup函数。这些功能大家平时可能用不上,但是还是要知道有这些功能,因为有的情况下这些功能能够派上大用场。
关注公众号:【前端欧阳】,给自己一个进阶vue的机会

另外欧阳写了一本开源电子书vue3编译原理揭秘,看完这本书可以让你对vue编译的认知有质的提升。这本书初、中级前端能看懂,完全免费,只求一个star。
盘点Vue3 watch的一些关键时刻能够大显身手的功能的更多相关文章
- [git] 能在关键时刻救命的git指令
* 查看所有分支的所有操作记录(关键时刻能救命) git reflog
- 花生壳的ddns 关键时刻又掉链子,准备迁到阿里万网
https://www.oray.com/news/affiche/?aid=628 免费版花生壳服务故障 因免费版机房线路节点负荷突然暴增,导致花生壳免费版登录缓慢或异常,或出现域名指向到127.0 ...
- 倔强的网站数据抓取,关键时刻还需Webbrowser显身手
由于最近台风挺多,公司网站上需要挂上台风预报信息,就整了个抓取台风数据(至于抓数据的概念和实践手册我以前写的一篇博客里面有介绍:分享一套抓数据小程序,客户资料.实时新闻.股票数据…随心抓)的服务,做调 ...
- iOS - 系统权限(关键时刻很有用的)
iOS开发中权限问题: APP开发避免不开系统权限的问题,如何在APP以更加友好的方式向用户展示系统权限,似乎也是开发过程中值得深思的一件事: 那如何提高APP获取iOS系统权限的通过率呢?有以下几种 ...
- Spring Boot 排除自动配置的 4 种方法,关键时刻很有用!
Spring Boot 提供的自动配置非常强大,某些情况下,自动配置的功能可能不符合我们的需求,需要我们自定义配置,这个时候就需要排除/禁用 Spring Boot 某些类的自动化配置了. 比如:数据 ...
- 关键时刻,让你的iphone拒绝掉的所有来电
夜间被骚扰电话吵醒是会非常烦躁的,以下就是iphone的勿扰模式,配合刚出的夜间深夜模式非常的nice. 可以自定义设置时间段,每天智能切换. 也可以开启个人收藏的白名单,让家人有紧急事情也可以联系到 ...
- 火爆全球的“饺子皮”3D手办原来是这样做的!关键时刻少不了远程控制软件!
2022年卡塔尔世界杯的吉祥物最近在全球火出圈了,并且喜提中国网友给予的爱称"饺子皮"."馄饨皮"(官方名字:拉伊卜,意为"技艺高超的球员" ...
- 金蝶盘点机PDA条码数据采集器WMS系统具体有哪些功能
1. 使用汉码盘点机PDA实现仓库条码管理的好处 (1) 传统电脑管理软件出入库需要来回电脑跑人工手工电脑录单效率低,通过人眼识别商品品种和清点商品数量,容易造成录单错误.从而造成电脑管理软件库存 ...
- 用Typescript 的方式封装Vue3的表单绑定,支持防抖等功能。
Vue3 的父子组件传值.绑定表单数据.UI库的二次封装.防抖等,想来大家都很熟悉了,本篇介绍一种使用 Typescript 的方式进行统一的封装的方法. 基础使用方法 Vue3对于表单的绑定提供了一 ...
- Vue2和Vue3技术整理1 - 入门篇 - 更新完毕
Vue2 0.前言 首先说明:要直接上手简单得很,看官网熟悉大概有哪些东西.怎么用的,然后简单练一下就可以做出程序来了,最多两天,无论Vue2还是Vue3,就都完全可以了,Vue3就是比Vue2多了一 ...
随机推荐
- 将微信小程序的代码上传到github
在微信小程序端上传的时候会报错,昨天整了一晚上,没有解决.今天偶然发现了解决方案,下面分为两种情况来说. 一.未生成git仓库 将一号区域的代码粘贴到微信小程序的终端即可 二.已生成了git仓库 将二 ...
- 常用 DNS 查询速度测试
测试工具 DNS-Benchmark | GitHub 总结 进过多轮测试,我认为: 223.5.5.5 和 223.6.6.6 平均响应时间最短(223.5.5.5 在教育网内疑似无法连通) 114 ...
- 在虚拟机CentOS中安装docker
公众号本文地址:在虚拟机CentOS中安装Docker 1.关闭防火墙 docker需要用到网络,所以需要关闭防火墙.进入管理员模式获得权限后进行关闭. su 关闭防火墙: systemctl dis ...
- 人形动画常见IK的处理
Unity中常见人形动画IK的处理方式 本文将尝试仅使用Untiy内置的Animator来解决常见的几种运动所需的IK.也会给出核心功能的代码实现. 效果一览:b站视频 Unity中人形角色的IK I ...
- RxJS 系列 – Transformation Operators
前言 前几篇介绍过了 Creation Operators Filter Operators Join Creation Operators Error Handling Operators 这篇继续 ...
- Kubernetes基础(基本概念、架构)(十)
一.介绍 Kubernetes(简称 K8S) 的出现是容器化技术发展的必然结果,容器化是应用程序级别的虚拟化,运行单个内核上有多个独立的用户空间实例,这些实例就是容器:容器提供了将应用程序的代码.运 ...
- mysql-存储过程(1) mysql循环语句
mysql循环语句: 本文总结了mysql常见的三种循环方式:while.repeat和loop循环.还有一种goto,不推荐使用. 一.while循环 delimiter // #定义标识符为双斜杠 ...
- iOS 14 UIDatePicker适配问题,使用老的选择器样式
iOS 14 UIDatePicker 在 13.4 新增了2个属性如下 @property (nonatomic, readwrite, assign) UIDatePickerStyle pref ...
- Solon 3.0 新特性:HttpUtils 了解一下
Solon 3.0 引入一个叫 HttpUtils 小插件,这是一个简单的同步 HTTP 客户端,基于 URLConnection 适配(也支持切换为 OkHttp 适配).使得编写 HTTP 客户端 ...
- 鲲鹏(ARM64)+麒麟(Kylin v10)离线部署 KubeSphere
作者:社区用户-天行1st 本文将详细介绍,如何基于鲲鹏 CPU(ARM64) 和操作系统 Kylin V10 SP2/SP3,利用 KubeKey 制作 KubeSphere 和 Kubernete ...