使用Vue3.5的onWatcherCleanup封装自动cancel的fetch函数

news2024/12/27 12:47:08

前言

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

加入欧阳的高质量vue源码交流群、欧阳平时写文章参考的多本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效果图:
click

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

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

从上图中可以看到在卸载组件时组件正在从服务端请求数据,此时请求会自动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函数。

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2124126.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

《 C++ 容器全景指南:五 》深入探索 C++ 标准库中的 stack 与 queue 容器适配器

1、引言 1.1、容器适配器的概念与应用 容器适配器&#xff08;Container Adapters&#xff09;是 C 标准库提供的一种特殊容器&#xff0c;它不是一种独立的容器&#xff0c;而是对其他标准容器的封装&#xff0c;用来实现特定的数据结构如栈&#xff08;stack&#xff09;和…

【信创】麒麟KOS上安装使用网络抓包工具Wireshark

原文链接&#xff1a;【信创】麒麟KOS上安装使用网络抓包工具Wireshark Hello&#xff0c;大家好啊&#xff01;今天给大家带来一篇关于如何在麒麟桌面操作系统上安装和使用Wireshark的文章。Wireshark是一款强大的网络协议分析工具&#xff0c;广泛应用于网络故障排查、网络流…

Makefile学习总结

Makefile学习总结 目录 Makefile学习总结1. Makefile介绍2. Makefile规则3. Makefile文件里的赋值方法4. Makefile常用函数4.1 字符串替换和分析函数4.2 文件名函数4.3 其他函数 5. Makefile使用示例6、多级目录通用Makefile Demo6.1 一般通用Makefile的设计思想6.2 Demo分析 参…

DAY73

作业 pro文件&#xff1a; QT texttospeech 头文件&#xff1a; #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QPushButton> //按钮类 #include <QLabel> //标签类 #include <QLineEdit> //行编译器类 #include…

【delphi】判断多显示器下,程序在那个显示器中

在 Delphi 中&#xff0c;如果你的电脑连接了多个显示器&#xff0c;可以通过以下步骤判断某个程序在哪个显示器上运行。 方法概述&#xff1a; 获取程序窗口的位置&#xff08;例如窗体的 Left、Top 坐标&#xff09;。使用 Screen.MonitorFromWindow 函数来确定该窗口所属的…

Hibernate QueryPlanCache 查询计划缓存引发的内存溢出

目录 1.排查方式2.结论3.解决办法 前言&#xff1a;在生产环境中有一个后端程序多次报oom然后导致程序中断。 1.排查方式 通过下载后端程序产生的oom文件&#xff0c;将oom文件导入MemoryAnalyzer程序分析程序堆内存使用情况。 1、将oom文件导入MemoryAnalyzer后可以看到概览信…

在银河麒麟服务器操作系统中设置SSH登录限制

在银河麒麟服务器操作系统中设置SSH登录限制 1、引言2、 步骤一&#xff1a;检查MaxStartups选项3、步骤二&#xff1a;修改MaxStartups选项4、步骤三&#xff1a;重启SSH服务 &#x1f496;The Begin&#x1f496;点点关注&#xff0c;收藏不迷路&#x1f496; 1、引言 在服务…

flask旧衣物捐赠系统—计算机毕业设计源码26577

摘要 科技进步的飞速发展引起人们日常生活的巨大变化&#xff0c;电子信息技术的飞速发展使得电子信息技术的各个领域的应用水平得到普及和应用。信息时代的到来已成为不可阻挡的时尚潮流&#xff0c;人类发展的历史正进入一个新时代。在现实运用中&#xff0c;应用软件的工作规…

【干货分享】Ftrans安全数据交换系统 搭建跨网数据传输通道

安全数据交换系统是一种专门设计用于在不同的网络、系统或组织之间安全地传输数据的软件或硬件解决方案。这种系统通常包含多种安全特性&#xff0c;以确保数据在传输过程中的保密性、完整性和可用性。 安全数据交换系统可以解决哪些问题&#xff1f; 安全数据交换系统主要解…

神经网络卷积层和最大池化

文章目录 一、卷积层原理二、相关函数的概念三、卷积层的应用四、最大池化原理五、最大池化案例 一、卷积层原理 ./ 当前目录&#xff1b;…/ 上级目录 父类&#xff08;也称为基类或超类&#xff09;是指在类继承体系中被其他类继承的类。也就是被其他子类进行调用的类 当In_…

「豆包 Marscode 体验官」AI 加持的云端 IDE——三种方法高效开发前后端聊天交互功能

以下是「豆包 MarsCode 体验官」优秀文章&#xff0c;作者努力的小雨。 豆包 MarsCode 豆包MarsCode 编程助手支持的 IDE: 支持 Visual Studio Code 1.67.0 及以上版本&#xff0c;以及 JetBrains 系列 IDE&#xff0c;如 IntelliJ IDEA、Pycharm 等&#xff0c;版本要求为 22…

016.PL-SQL编程—过程

我 的 个 人 主 页&#xff1a;&#x1f449;&#x1f449; 失心疯的个人主页 &#x1f448;&#x1f448; 入 门 教 程 推 荐 &#xff1a;&#x1f449;&#x1f449; Python零基础入门教程合集 &#x1f448;&#x1f448; 虚 拟 环 境 搭 建 &#xff1a;&#x1f449;&…

服务器测试之GPU基础汇总

GPU基础汇总 1.GPU简介 1.1.什么是GPU GPU英文全称Graphic Processing Unit&#xff0c;中文翻译为“图形处理器”。一个专门的图形核心处理器。GPU是显示卡的“大脑”&#xff0c;决定了该显卡的档次和大部分性能&#xff0c;同时也是2D显示卡和3D显示卡的区别依据。可以形…

Flask 第三课 -- 第一个应用

上一章节我们已经成功安装了 Flask&#xff0c;接下来我们可以创建一个简单的 Flask 应用。 首先&#xff0c;创建一个名为 app.py 的文件&#xff0c;并添加以下内容&#xff1a; from flask import Flaskapp Flask(__name__)app.route(/) def hello_world():return Hello,…

网络拓扑结构介绍

这张图展示了一个复杂的网络拓扑结构&#xff0c;它包括了多个运营商的接入、负载均衡、安全防护以及数据处理等多个关键环节。整个网络通过精心设计的架构和高效的节点连接&#xff0c;实现了数据的快速传输和安全处理。 一、各个模块介绍 运营商接入&#xff1a; 移动、电信…

论文速读|形机器人的高速和抗冲击远程操作

论文地址&#xff1a;https://arxiv.org/pdf/2409.04639 本文提出了一种综合解决方案&#xff0c;用于远程控制类人机器人&#xff0c;实现了高速度和冲击抵抗的操作。通过结合无校准的运动捕捉和重定标、低延迟全身运动流式传输工具箱和高带宽的摆线驱动器&#xff0c;显著提高…

【Python报错已解决】ValueError: All arrays must be of the same length

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏: 《C干货基地》《粉丝福利》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 文章目录 前言一、问题描述1.1 报错示例1.2 报错分析1.3 解决思路 二、解决方法2.1 方法一&#xff1a;调整数组长度2.2 步骤二…

数据流图的例题2

答案选B 解析&#xff1a; 第一个错误&#xff0c;E1和E2是外部实体&#xff0c;实体之间不可能有数据传输&#xff0c;DF2错误 第二个错误&#xff0c;DF6是外部实体把数据传到存储。外部实体必须把数据传到加工&#xff0c;进行加工之后才能对数据存储&#xff0c;DF6错误…

Git环境搭建

我的博客大纲 我的GIT学习大纲 Git安装步骤&#xff1a; 1.官网地址 查看 GNU 协议&#xff0c;可以直接点击下一步&#xff1a; 2.Git配置选项如下&#xff1a; 3.选择后台客户端连接协议&#xff0c;选默认值 OpenSSL&#xff0c;然后下一步。 4.Git换行符号 5.选择终端类型…

Lesson08---string类(2)

1.assign assign的功能就类似于把string里面原来有的东西清空然后重新赋值 但是重新赋值也可以达到一样的效果感觉没什么用&#xff0c;了解一下就行 2.insert 第一个参数是在第几个位置插入&#xff0c;第二个参数是插入的字符串 但是这里不得不吐槽一下这里只能是字符串&…