前言

在欧阳的上一篇 这应该是全网最详细的Vue3.5版本解读文章中有不少同学对Vue3.5新增的onWatcherCleanup有点疑惑,这个新增的API好像和watch API回调的第三个参数onCleanup功能好像重复了。今天这篇文章来讲讲新增的onWatcherCleanup函数的使用场景:封装一个自动cancel的fetch函数

关注公众号:【前端欧阳】,给自己一个进阶vue的机会

watch回调的第三个参数onCleanup

有些同学可能还不清楚watch回调的第三个参数onCleanup,我们先来看个demo,代码如下:

watch(id, (value, oldValue, onCleanup) => {
console.log("do something");
onCleanup(() => {
console.log("cleanup");
});
});

watch回调的前两个参数大家应该很熟悉,分别是value新的值,oldValue旧的值。

第三个参数onCleanup大家平时可能用的不多,这是一个回调函数,当watch的值改变后或者组件销毁前就会执行onCleanup传入的回调。

在上面的demo中就是变量id改变时会触发onCleanup中的回调,进而console打印"cleanup"字符串。又或者所在的组件销毁前也会触发onCleanup中的回调,进而console打印"cleanup"字符串。

那我们在onCleanup中可以干嘛呢?

答案是可以清理副作用,比如在watch中使用setInterval初始化一个定时器。那么我们就可以在onCleanup的回调中清理掉定时器,无需去组件的beforeUnmount钩子函数去统一清理。

onWatcherCleanup函数

onWatcherCleanup函数的作用和watch回调的第三个参数onCleanup差不多,也是当watch的值改变后或者组件销毁前就会执行onWatcherCleanup传入的回调。

使用方法也很简单,代码如下:

import { watch, onWatcherCleanup } from "vue";

watch(id, () => {
console.log("do something");
onWatcherCleanup(() => {
console.log("cleanup");
});
});

从上面的代码可以看到onWatcherCleanup的用法其实和watch回调的第三个参数onCleanup差不多,区别在于这里的onWatcherCleanup是从vue中import导入的。

除了从vue中import导入的区别以外,还有一个区别是onWatcherCleanup不光在watch中可以使用,在watchEffect中同样也可以使用。比如下面这样的:

watchEffect(() => {
console.log("do something in watchEffect", id.value);
onWatcherCleanup(() => {
console.log("cleanup watchEffect");
});
});

和前面的例子一样,上面的代码中id的值改变后或者组件销毁时也会执行onWatcherCleanup函数中的console.log打印。

onWatcherCleanup函数是从vue中import导入的,那么这意味着onWatcherCleanup函数的调用可以写在任意地方,只要最终经过函数的层层调用后还是在watch或者watchEffect的回调中就可以。

利用上面的这一特点我们可以使用onWatcherCleanup做到一些onCleanup做不到的事情,比如:封装一个自动cancelfetch函数。

封装自动cancel的fetch函数

在讲这个之前我们先来了解一下如何cancel一个fetch函数。

这里涉及到AbortController接口,AbortController 接口表示一个控制器对象,允许你根据需要中止一个或多个 Web 请求。

下面这个是cancel取消一个请求的demo,代码如下:

const controller = new AbortController();
const res = await fetch(url, {
...options,
signal: controller.signal,
}); setTimeout(() => {
controller.abort();
}, 500);

首先使用new AbortController()创建一个控制器对象controller

其中的controller.signal返回一个 AbortSignal 对象实例,可以用它来和异步操作进行通信或者中止这个操作。

在我们这里把controller.signal作为signal选项直接传给fetch函数就可以了。

最后就是可以使用controller.abort()将fetch请求取消掉,在上面的demo中是如果超过500ms请求还没完成,那么就执行controller.abort()将fetch请求取消掉。

有了前面的知识铺垫,我们先来看看使用“自动cancelfetch函数”的地方,代码如下:

<script setup lang="ts">
import { watch, ref, watchEffect, onWatcherCleanup } from "vue";
import myFetch from "./myFetch"; const id = ref(1);
const data = ref(null); watch(id, async () => {
const res = await myFetch(`http://localhost:3000/api/${id.value}`, {
method: "GET",
});
console.log(res);
data.value = res;
});
</script> <template>
<p>data is: {{ data }}</p>
<button @click="id++">id++</button>
</template>

在上面的例子中使用watch监听了变量id,在监听的回调中会使用封装的myFetch函数请求接口。

上面的例子大家平时应该经常遇到,如果id的值变化很快,但是服务端接口请求需要2秒才能完成,这时我们期望只有最后一次id的值改变触发的请求才需要完成,其他请求都cancel取消掉。

如果在myFetch请求的过程中组件被销毁了,此时我们也期望能够将请求cancel取消掉。

在Vue3.5之前想要去实现上面的这两个需求很麻烦,但是有了Vue3.5的onWatcherCleanup函数后就非常容易了。

这个是封装的自动cancelfetch函数,myFetch.ts文件代码如下:

import { getCurrentWatcher, onWatcherCleanup } from "vue";

export default async function myFetch(url: string, options: RequestInit) {
const controller = new AbortController();
if (getCurrentWatcher()) {
onWatcherCleanup(() => {
controller.abort();
});
} const res = await fetch(url, {
...options,
signal: controller.signal,
}); let json;
try {
json = await res.json();
} catch (error) {
json = {
code: 500,
message: "JSON format error",
};
}
return json;
}

由于onWatcherCleanup函数是从vue中import导入,那么我们就可以在自己封装的myFetch函数中导入和使用他。

onWatcherCleanup函数的回调中我们执行了controller.abort(),前面已经讲过了当watch或者watchEffect的回调执行前或者组件卸载前就会执行里面的onWatcherCleanup注册的回调。我们这里的myFetch是在watch中调用的,当然也会触发里面的onWatcherCleanup注册的回调。

onWatcherCleanup的回调中执行了controller.abort(),前面我们讲过了执行controller.abort()就会将正在请求的fetch函数给cancel取消掉。

就这么简单的就实现了前面的两个需求:

需求一:如果id的值变化很快,但是服务端接口请求需要2秒才能完成,这时我们期望只有最后一次id的值改变触发的请求才需要完成,其他请求都cancel取消掉。下面这个是变量id在短时间内多次修改的gif效果图:

从上面的gif图可以看到只有最后一个请求是完成了的,其他请求全部被cancel掉。

需求二:如果在myFetch请求的过程中组件被销毁了,此时我们也期望能够将请求cancel取消掉。下面这个是组件卸载时gif效果图:

从上图中可以看到在卸载组件时组件正在从服务端请求数据,此时请求会自动cancel掉。

细心的小伙伴发现了在myFetch函数中,onWatcherCleanup函数外面套了一个getCurrentWatcher的判断,代码如下:

import { getCurrentWatcher, onWatcherCleanup } from "vue";

export default async function myFetch(url: string, options: RequestInit) {
// ...省略
if (getCurrentWatcher()) {
onWatcherCleanup(() => {
controller.abort();
});
}
// ...省略
}

当watch或者watchEffect监听的值改变后onWatcherCleanup的回调就会触发,所以onWatcherCleanup的执行是由其所在的watch或者watchEffect触发的。

如果onWatcherCleanup不在watch或者watchEffect的回调中执行,那么当然onWatcherCleanup中的回调也永远不会执行。

可能有的小伙伴有疑问,你这里的onWatcherCleanup是在myFetch中执行的,也没在watch或者watchEffect的回调中执行吖?

答案是myFetch函数的执行是在watch中执行的,myFetch然后再去执行onWatcherCleanup

getCurrentWatcher()函数就会返回当前正在执行回调的watch或者watchEffect,如果当前myFetch不是在watch或者watchEffect的回调中执行的,那么getCurrentWatcher()函数的返回值就是空,所以这种情况就不需要去执行onWatcherCleanup函数了。

最后值得一提的是onWatcherCleanup不能在await后面执行,比如下面这样的代码:

import { getCurrentWatcher, onWatcherCleanup } from "vue";

export default async function myFetch(url: string, options: RequestInit) {
const controller = new AbortController();
const res = await fetch(url, {
...options,
signal: controller.signal,
}); let json;
try {
json = await res.json();
} catch (error) {
json = {
code: 500,
message: "JSON format error",
};
}
// 错误的写法
if (getCurrentWatcher()) {
onWatcherCleanup(() => {
controller.abort();
});
} return json;
}

在上面的代码中我们将onWatcherCleanup调用放在了await fetch()的后面,这种写法onWatcherCleanup注册的回调是不会执行的

为什么在await后面的onWatcherCleanup注册的回调永远不会执行呢?

答案是js的await相当于注册了一个回调函数去执行await后的代码,当await等待结束后再去执行这个回调函数,从而执行await后的代码。

await以及之前的代码确实是在watch回调中执行的,我们这里的onWatcherCleanup就是await后面的代码,await后面的代码是在一个新的回调中执行的,也就是watch“回调中”的“回调中”执行的。

onWatcherCleanup执行时已经不知道当前正在执行的watch回调是谁了,所以onWatcherCleanup的回调也没注册上。当watch的变量修改时或者组件卸载时onWatcherCleanup注册的回调永远也不会执行。

总结

watch或者watchEffect监听的变量修改时,以及组件卸载时,会去执行他们回调中使用onWatcherCleanup注册的回调函数。并且onWatcherCleanup是从vue中import导入的,使得我的可以在任意地方执行onWatcherCleanup函数。利用这两个特性我们就可以封装一个自动cancel的fetch函数。

关注公众号:【前端欧阳】,给自己一个进阶vue的机会

另外欧阳写了一本开源电子书vue3编译原理揭秘,看完这本书可以让你对vue编译的认知有质的提升。这本书初、中级前端能看懂,完全免费,只求一个star。

使用Vue3.5的onWatcherCleanup封装自动cancel的fetch函数的更多相关文章

  1. [Javascript] AbortController to cancel the fetch request

    We are able to cancel the fetch request by using AbortController with RxJS Observable. return Observ ...

  2. Vue3 生命周期 && Hooks封装 && toRef

    1 # 一.Vue3.0与Vue2.0生命周期改动 2 beforDestroy改名为beforeUnmount 3 destroyed改名为unmounted 4 # Vue3.0页提供了Compo ...

  3. AngularJS driective 封装 自动滚动插件

    1.ui-smooth-scroll.js文件内容 angular.module('app') .directive('uiSmoothScroll', ['$location', '$anchorS ...

  4. vue3 打开页面input框自动获得焦点

    1.需要聚焦的el-input输入框设置ref值: ref="getfcous" <el-input v-model="workorder" ref=&q ...

  5. Vue3.0工程创建 && setup、ref、reactive函数 && Vue3.0响应式实现原理

    1 # 一.创建Vue3.0工程 2 # 1.使用vue-cli创建 3 # 官方文档: https://cli.vuejs.org/zh/guide/creating-a-project.html# ...

  6. ThinkPHP 3.2.3 自动加载公共函数文件的方法

    方法一.加载默认的公共函数文件 在 ThinkPHP 3.2.3 中,默认的公共函数文件位于公共模块 ./Application/Common 下,访问所有的模块之前都会首先加载公共模块下面的配置文件 ...

  7. ThinkPHP3自动加载公共函数文件

    7d 根目录 ├─Application 应用目录 │ ├─Common 公共模块 │ │ ├─Common 公共函数文件目录 │ │ │ ├─index.html │ │ ├─Config 配置文件 ...

  8. php自动获取字符串编码函数mb_detect_encoding(转)

    使用 mb_detect_encoding() 函数来判断字符串是什么编码的. 当在php中使用mb_detect_encoding函数进行编码识别时,很多人都碰到过识别编码有误的问题,例如对与GB2 ...

  9. php自动获取字符串编码函数mb_detect_encoding

    当在php中使用mb_detect_encoding函数进行编码识别时,很多人都碰到过识别编码有误的问题,例如对与GB2312和UTF- 8,或者UTF-8和GBK(这里主要是对于cp936的判断), ...

  10. PHP目录操作(附封装好的目录操作函数文件)

    目录函数库常用API $path='test'; var_dump(is_dir($path));//检测是否为目录 echo '<hr/>'; echo getcwd();//得到当前的 ...

随机推荐

  1. HBase 在统一内容平台业务的优化实践

    作者:来自 vivo 互联网服务器团队-Leng Jianyu.Huang Haitao HBase是一款开源高可靠性.扩展性.高性能和灵活性的分布式非关系型数据库,本文围绕数据库选型以及使用HBas ...

  2. MySQL索引是怎么支撑千万级表的快速查找?

    前言 在 MySQL 官方提到,改善操作性能的最佳方法 SELECT 在查询中测试的一个或多个列上创建索引.索引条目的作用类似于指向表行的指针,从而使查询可以快速确定哪些行与WHERE子句中的条件匹配 ...

  3. card 卡片 html

    {% extends 'base.html' %} {% block content %} <div class="container"> <h1>客户信息 ...

  4. scratch少儿编程卡通三国背景72张全套素材包【免费下载】

    scratch卡通三国题材背景图片,共72张,让你轻松打造scratch三国世界! 免费下载地址:https://www.xiaohujing.com.cn 这套背景图片以卡通风格呈现,色彩鲜艳.造型 ...

  5. nginx实现 springboot项目的负载均衡 策略

    weight 代表权重,默认为1,权重越高被分配的客户端越多 指定轮询几率,weight和访问比率成正比,用于后端服务器性能不均的情况. 例如 # 反向代理配置upstream server_list ...

  6. 使用 `useServerSeoMeta` 优化您的网站 SEO

    title: 使用 useServerSeoMeta 优化您的网站 SEO date: 2024/7/31 updated: 2024/7/31 author: cmdragon excerpt: 摘 ...

  7. SemanticKernel/C#:使用Ollama中的对话模型与嵌入模型用于本地离线场景

    前言 上一篇文章介绍了使用SemanticKernel/C#的RAG简易实践,在上篇文章中我使用的是兼容OpenAI格式的在线API,但实际上会有很多本地离线的场景.今天跟大家介绍一下在Semanti ...

  8. 1、Git简介

    1.1.概述 Git 是一个开源免费的分布式版本控制系统,用于快速高效地管理各种小型或大型项目的代码. Git 不仅容易学习.占用空间小,而且性能快如闪电. Git 具有廉价的本地分支.方便的暂存区域 ...

  9. 【Spring】03 XML配置

    Alias别名设置 可以为一个Bean的ID再设置一个ID 多一个可用标识,大概... 在获取实例注入参数时,两个标识都可以使用 除了Alias可以设置别名之外,Bean的标签本身也可以设置第二别名 ...

  10. 强化学习入门书籍《DeepReinforcementLearningHands-On-SecondEdition》

    前段时间在网上买了本强化学习入门的书籍,即<Deep-Reinforcement-Learning-Hands-On>,虽然是影印版的,但是感觉还是可以看看的,说的也蛮易懂的,感觉比现在市 ...