手写中实现并学习ahooks——useRequest
前言
最近业务没有之前紧张了,也是消失了一段时间,也总结了一些之前业务上的问题。
和同事沟通也是发现普通的async
+ await
+ 封装api
在复杂业务场景下针对于请求的业务逻辑比较多,也是推荐我去学习一波ahooks,由于问题起源于请求,因此作者也是直接从 useRequest
开始看起。
附ahooks useRequest
链接:
https://ahooks-v2.js.org/zh-CN/hooks/async/
实现
话不多说,手写直接开始,参考几个比较常用的 useRequest
能力来一个个实现吧。
基础版(雏形)
先上代码:
useRequest.ts
interface UseRequestOptionsProps {/** 请求参数*/initialData?: object;/** 请求成功回调*/onSuccess?: (res: any) => void;
}const useRequest = (requestFn: (initialData?: object | string | [],) => Promise<SetStateAction<any>>,options: UseRequestOptionsProps,
) => {const [data, setData] = useState<SetStateAction<any>>(null);const [loading, setLoading] = useState<boolean>(false);const [error, setError] = useState<string | null>(null);const { initialData, onSuccess } = options;useEffect(() => {setLoading(true);setError(null);setData(null);request();}, [requestFn]);// useRequest业务逻辑const request = async () => {try {const res = await requestFn(initialData);setData(res);// 请求成功响应回调onSuccess && onSuccess(res);} catch (err) {err && setError(JSON.stringify(err));} finally {setLoading(false);}};return { data, loading, error };
};export default useRequest;
使用
const { data, loading, error } = useRequest(queryCompensatoryOrderSituation,{initialData: {compensatoryId,}onSuccess: (res) => {console.log('success request!', res);},},
);
useRequest
对于请求函数的写法并无过多要求,只要是一个异步function
且返回一个promise
对象,即可传入useRequest
的第一个参数中,而第二个参数则是一系列的可选配置项,雏形版本我们暂时只支持onSuccess
。
手动触发
代码改造后:
useRequest.ts
interface UseRequestOptionsProps {/** 手动开启*/manual?: boolean;/** 请求参数*/initialData?: object;/** 请求成功回调*/onSuccess?: (res: any) => void;
}const useRequest = (requestFn: (initialData?: object | string | [],) => Promise<SetStateAction<any>>,options: UseRequestOptionsProps,
) => {const [data, setData] = useState<SetStateAction<any>>(null);const [loading, setLoading] = useState<boolean>(false);const [error, setError] = useState<string | null>(null);const { manual, initialData, onSuccess } = options;useEffect(() => {setLoading(true);setError(null);setData(null);!manual && request();}, [manual]);// useRequest业务逻辑const request = async () => {try {const res = await requestFn(initialData);setData(res);// 请求成功响应回调onSuccess && onSuccess(res);} catch (err) {err && setError(JSON.stringify(err));} finally {setLoading(false);}};return { data, loading, error, request };
};export default useRequest;
使用
const { data, loading, error, request } = useRequest(queryCompensatoryOrderSituation,{manual: true,initialData: {compensatoryId,},onSuccess: (res) => {console.log('success request!', res);},},
);request();
手动执行的逻辑主要是根据manual
参数砍掉useRequest mount
阶段的渲染请求,把执行请求的能力暴露出去,在页面中去手动调用request()
来触发。
轮询与手动取消
代码改造后:
useRequest.ts
interface UseRequestOptionsProps {/** 手动开启*/manual?: boolean;/** 请求参数*/initialData?: object;/** 轮询*/pollingInterval?: number | null;/** 请求成功回调*/onSuccess?: (res: any) => void;
}const useRequest = (requestFn: (initialData?: object | string | [],) => Promise<SetStateAction<any>>,options: UseRequestOptionsProps,
) => {const [data, setData] = useState<SetStateAction<any>>(null);const [loading, setLoading] = useState<boolean>(false);const [error, setError] = useState<string | null>(null);const status = useRef<boolean>(false);const pollingIntervalTimer = useRef<NodeJS.Timer | null>(null);const { manual, initialData, pollingInterval, onSuccess } = options;useEffect(() => {setLoading(true);setError(null);setData(null);!manual && request();}, [manual]);// useRequest业务逻辑const request = async () => {try {!status.current && (status.current = true);if (pollingInterval && status.current) {pollingIntervalTimer.current = setTimeout(() => {status.current && request();}, pollingInterval);}const res = await requestFn(initialData);setData(res);// 请求成功响应回调onSuccess && onSuccess(res);} catch (err) {err && setError(JSON.stringify(err));} finally {setLoading(false);}};return { data, loading, error, request, cancel };
};// 取消
const cancel = () => {if (pollingIntervalTimer.current) {clearTimeout(pollingIntervalTimer.current);pollingIntervalTimer.current = null;status.current && (status.current = false);}
};export default useRequest;
使用
const { data, loading, error, request, cancel } = useRequest(queryCompensatoryOrderSituation,{manual: true,initialData: {compensatoryId,},pollingInterval: 1000,onSuccess: (res) => {console.log('success request!', res);},},
);request();...
// 轮询到理想数据后
cancel();
轮询的支持在hook中主要用到了timer setTimeout
的递归思路,同时给出一个status
状态值判断是否在轮询中,当调用端执行cancel()
,status
则为false
;当轮询开始,则status
为true
。
而cancel()
的能力主要也是取消了timer
的递归请求逻辑,并且轮询的业务场景和manual: true
配合很多。
依赖请求(串型请求)
代码改造后:
useRequest.ts
interface UseRequestOptionsProps {/** 手动开启*/manual?: boolean;/** 请求参数*/initialData?: object;/** 轮询*/pollingInterval?: number | null;/** 准备,用于依赖请求*/ready?: boolean;/** 请求成功回调*/onSuccess?: (res: any) => void;
}const useRequest = (requestFn: (initialData?: object | string | [],) => Promise<SetStateAction<any>>,options: UseRequestOptionsProps,
) => {const [data, setData] = useState<SetStateAction<any>>(null);const [loading, setLoading] = useState<boolean>(false);const [error, setError] = useState<string | null>(null);const status = useRef<boolean>(false);const pollingIntervalTimer = useRef<NodeJS.Timer | null>(null);const {manual,initialData,pollingInterval,ready = true,onSuccess,} = options;useEffect(() => {setLoading(true);setError(null);setData(null);!manual && ready && request();}, [manual, ready]);// useRequest业务逻辑const request = async () => {try {!status.current && (status.current = true);if (pollingInterval && status.current) {pollingIntervalTimer.current = setTimeout(() => {status.current && request();}, pollingInterval);}const res = await requestFn(initialData);setData(res);// 请求成功响应回调onSuccess && onSuccess(res);} catch (err) {err && setError(JSON.stringify(err));} finally {setLoading(false);}};return { data, loading, error, request, cancel };
};// 取消
const cancel = () => {if (pollingIntervalTimer.current) {clearTimeout(pollingIntervalTimer.current);pollingIntervalTimer.current = null;status.current && (status.current = false);}
};export default useRequest;
使用
const [mountLoading, setMountLoading] = useState<boolean>(false);useEffect(() => {setMountLoading(true);
}, [2000])const { data, loading, error, request, cancel } = useRequest(queryCompensatoryOrderSituation,{initialData: {compensatoryId,},pollingInterval: 1000,ready: mountLoading,onSuccess: (res) => {console.log('success request!', res);},},
);
依赖请求的思路就是在hook
中加入一个ready
字段,也是在基于manual
一层的限制后又加了一层,来判断是否在hook
加载时是否做默认请求,而当option
中的ready
更新(为true)时,hook自动更新从而发起请求。
常用于页面中A请求完成后执行B请求,B请求的ready
字段依赖于A请求的data
/loading
字段。
防抖与节流
防抖和节流的实现比较简单,依赖于lodash
库,包装了一下request
函数的请求内容。
代码如下:
useRequest.ts
interface UseRequestOptionsProps {/** 手动开启*/manual?: boolean;/** 请求参数*/initialData?: object;/** 轮询*/pollingInterval?: number | null;/** 准备,用于依赖请求*/ready?: boolean;/** 防抖*/debounceInterval?: number;/** 节流*/throttleInterval?: number;/** 请求成功回调*/onSuccess?: (res: any) => void;
}const useRequest = (requestFn: (initialData?: object | string | [],) => Promise<SetStateAction<any>>,options: UseRequestOptionsProps,
) => {const [data, setData] = useState<SetStateAction<any>>(null);const [loading, setLoading] = useState<boolean>(false);const [error, setError] = useState<string | null>(null);const status = useRef<boolean>(false);const pollingIntervalTimer = useRef<NodeJS.Timer | null>(null);const {manual,initialData,pollingInterval,ready = true,debounceInterval,throttleIntervalonSuccess,} = options;useEffect(() => {setLoading(true);setError(null);setData(null);!manual && ready && request();}, [manual, ready]);// 请求const request = () => {if (debounceInterval) {lodash.debounce(requestDoing, debounceInterval)();} else if (throttleInterval) {lodash.throttle(requestDoing, throttleInterval)();} else {requestDoing();}
};// useRequest业务逻辑
const requestDoing = async () => {try {!status.current && (status.current = true);if (pollingInterval && status.current) {pollingIntervalTimer.current = setTimeout(() => {status.current && request();}, pollingInterval);}const res = await requestFn(initialData);setData(res);// 请求成功响应回调onSuccess && onSuccess(res);} catch (err) {err && setError(JSON.stringify(err));} finally {setLoading(false);}
};// 取消
const cancel = () => {if (pollingIntervalTimer.current) {clearTimeout(pollingIntervalTimer.current);pollingIntervalTimer.current = null;status.current && (status.current = false);}
};export default useRequest;
使用
const { data, loading, error, request, cancel } = useRequest(queryCompensatoryOrderSituation,{manual: true,initialData: {compensatoryId,},debounceInterval: 1000, // 防抖throttleInterval: 1000, // 节流onSuccess: (res) => {console.log('success request!', res);},},
);for(let i = 0; i < 10000; i++) {request();
}
在hook
中,通过lodash.debounce/lodash.throttle
来包装request
函数主体,通过option
中的判断来执行对应的包装体函数。
缓存与依赖更新
改造后的代码(最终代码)如下:
useRequest.ts
import {useState,useEffect,useRef,SetStateAction,useCallback,
} from 'react';
import lodash from 'lodash';interface UseRequestOptionsProps {/** 手动开启*/manual?: boolean;/** 请求参数*/initialData?: object;/** 轮询*/pollingInterval?: number | null;/** 准备,用于依赖请求*/ready?: boolean;/** 防抖*/debounceInterval?: number;/** 节流*/throttleInterval?: number;/** 延迟loading为true的时间*/loadingDelay?: number;/** 依赖*/refreshDeps?: any[];/** 请求成功回调*/onSuccess?: (res: any) => void;
}const useRequest = (requestFn: (initialData?: object | string | [],) => Promise<SetStateAction<any>>,options: UseRequestOptionsProps,
) => {const [data, setData] = useState<SetStateAction<any>>(null);const [loading, setLoading] = useState<boolean>(false);const [error, setError] = useState<string | null>(null);const status = useRef<boolean>(false);const pollingIntervalTimer = useRef<NodeJS.Timer | null>(null);const {manual,initialData,pollingInterval,ready = true,debounceInterval,throttleInterval,loadingDelay,refreshDeps,onSuccess,} = options;useEffect(() => {if (loadingDelay) {setTimeout(() => {status && setLoading(true);}, loadingDelay);}setError(null);setData(null);// 手动触发request!manual && ready && request();}, [manual, ready, ...(Array.isArray(refreshDeps) ? refreshDeps : [])]);// 请求const request = () => {if (debounceInterval) {lodash.debounce(requestDoing, debounceInterval)();} else if (throttleInterval) {lodash.throttle(requestDoing, throttleInterval)();} else {requestDoing();}};// useRequest业务逻辑const requestDoing = async () => {try {!status.current && (status.current = true);if (pollingInterval && status.current) {pollingIntervalTimer.current = setTimeout(() => {status.current && request();}, pollingInterval);}const res = await requestFn(initialData);setData(res);// 请求成功响应回调onSuccess && onSuccess(res);} catch (err) {err && setError(JSON.stringify(err));} finally {setLoading(false);}};// 取消const cancel = () => {if (pollingIntervalTimer.current) {clearTimeout(pollingIntervalTimer.current);pollingIntervalTimer.current = null;status.current && (status.current = false);}};// 缓存const cachedFetchData = useCallback(() => data, [data]);return { data, loading, error, request, cancel, cachedFetchData };
};export default useRequest;
使用
const [mountLoading, setMountLoading] = useState<boolean>(false);
const [updateLoading, setUpdateLoading] = useState<boolean>(false);setTimeout(() => {setMountLoading(true);
}, 1000);setTimeout(() => {setUpdateLoading(true);
}, 2000);const { data, loading, error, request, cancel, cachedFetchData } = useRequest(queryCompensatoryOrderSituation,{manual: true,initialData: {compensatoryId,},debounceInterval: 1000, // 防抖throttleInterval: 1000, // 节流refreshDeps: [mountLoading, updateLoading],onSuccess: (res) => {console.log('success request!', res);},},
);
缓存的主体思路是在useRequest
中拿到第一次数据后通过useCallback
来透出data
依赖来保存,同时向外暴露一个cachedFetchData
来过渡data
从null
到请求到接口数据的过程。
依赖更新的思路则是在页面中给useRequest
一系列依赖状态一并加入在hook
的请求副作用中,监听到页面中依赖改变,则重新请求,具体实现则是refreshDeps
参数。
结尾
花了一上午时间,一个简易版本的useRequest
实现了,也是通过实现学习到了一些请求思路,在业务复杂的场景下也是很需要这类请求工具来让开发者的注意力从请求处理转移集中在业务逻辑中。
相关文章:
手写中实现并学习ahooks——useRequest
前言 最近业务没有之前紧张了,也是消失了一段时间,也总结了一些之前业务上的问题。 和同事沟通也是发现普通的async await 封装api在复杂业务场景下针对于请求的业务逻辑比较多,也是推荐我去学习一波ahooks,由于问题起源于请求…...

[手写OS]动手实现一个OS 之 准备工作以及引导扇区
[手写OS]动手实现一个OS之第一步-环境以及引导扇区 环境准备 一台可用计算机(linux我不知道,我用的Windows)汇编编译器NASM一个方便的软盘读写工具VirtualBox 汇编编译器NASM 官网地址:https://www.nasm.us/pub/nasm/snapshot…...

JVM实战OutOfMemoryError异常
目录 Java堆溢出 常见原因: 虚拟机栈和本地方法栈溢出 实验1:虚拟机栈和本地方法栈测试(作为第1点测试程序) 实验2:(作为第1点测试程序) 运行时常量池和方法区溢出 运行时常量池内存溢出 …...
C++虚函数操作指南
1 什么是虚函数?1.1 虚函数的使用规则1.2 用 C 运行虚函数的示例1.3 协变式返回类型2 在 C 中使用虚函数的优点2.1 代码更为灵活、更为通用2.2 代码可复用2.3 契约式设计3 虚函数的局限性3.1 性能3.2 设计问题3.3 调试,容易出错4 虚函数的替代方案4.1 仅…...
Mybatis-Plus分页插件
引言:MyBatis Plus自带分页插件,只要简单的配置即可实现分页功能 1.添加Configuration配置类 Configuration MapperScan("com.atguigu.mybatisplus.mapper") //可以将主类中的注解移到此处public class MybatisPlusConfig {Beanpublic Mybatis…...
Selenium Webdriver options的实用参数设置
1、关闭Chrome浏览器受自动控制的提示 options.add_experimental_option(useAutomationExtension, False) options.add_experimental_option(excludeSwitches, [enable-automation])2、关闭是否保存密码的弹窗 options.add_experimental_option("prefs", { "c…...

代码随想录算法训练营第七天|454.四数相加II 、 383. 赎金信 、 15. 三数之和 、18. 四数之和
454.四数相加II 454.四数相加II介绍给你四个整数数组 nums1、nums2、nums3 和 nums4 ,数组长度都是 n ,请你计算有多少个元组 (i, j, k, l) 能满足:思路因为是存放在数组里不同位置的元素,因此不需要考虑去重的操作,而…...

详解抓包原理以及抓包工具whistle的用法
什么是抓包? 分析网络问题业务分析分析网络信息流通量网络大数据金融风险控制探测企图入侵网络的攻击探测由内部和外部的用户滥用网络资源探测网络入侵后的影响监测链接互联网宽频流量监测网络使用流量(包括内部用户,外部用户和系统)监测互联网和用户电脑的安全状…...

【C++】反向迭代器
文章目录一、什么是反向迭代器二、STL 源码中反向迭代器的实现三、reverse_iterator 的模拟实现四、vector 和 list 反向迭代器的实现一、什么是反向迭代器 C 中一共有四种迭代器 – iterator、const_iterator、reverse_iterator 以及 const_reverse_iterator,其中…...

(蓝桥真题)扫描游戏(计算几何+线段树二分)
题目链接:P8777 [蓝桥杯 2022 省 A] 扫描游戏 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 样例输入: 5 2 0 1 1 0 3 2 4 3 5 6 8 1 -51 -33 2 样例输出: 1 1 3 4 -1 分析:先考虑如何对物件进行排序,首先&…...

面试官:什么是双亲委派模型?如何打破它?
本文已经收录进 JavaGuide(「Java学习+面试指南」一份涵盖大部分 Java 程序员所需要掌握的核心知识。) 参加过校招面试的同学,应该对这个问题不陌生。一般提问 JVM 知识点的时候,就会顺带问你双亲委派模型(别扭的翻译。。。)。 就算是不准备面试,学习双亲委派模型对于我…...

自建服务器系列- DDNS配置
1、环境说明 光猫桥接路由器拔号的模式 2、DDNS是什么 对于DHCP方式获得的IP,无论对于局域网内来说,还是外网来说,都会有使得IP地址每隔一段时间变化一次,如果想要通过恒定不变的地址访问主机,就需要动态域名解析。…...
vue中使用axios简单封装用法,axios报错the request was rejected because no multipart boundar
在这里插入代码片## 创建实例 //这个写法作为我错误的记录,可以不看暂时 transformRequest: [(data: any) > {if (!data) {data {}}return qs.stringify(data)}]在我的项目里面,初始化配置里面进行handers的修改,例如:例如将…...
Leetcode.1220 统计元音字母序列的数目
题目链接 Leetcode.1220 统计元音字母序列的数目 Rating : 1730 题目描述 给你一个整数 n,请你帮忙统计一下我们可以按下述规则形成多少个长度为 n的字符串: 字符串中的每个字符都应当是小写元音字母(a, e, i, o, u)…...

深入元空间
元空间是干嘛的?元空间存储的是类的相关信息,就是类的运行时表达。包括:Class文件类的结构和方法常量注解代码优化JDK1.8分界在1.8版本之前,类的meta信息、类变量、字符串常量池都存储在永久代。1.8版本以后,类变量、实…...

前端技术和框架
一、各种技术概述 1.HTML 🧨HTML中文称为超文本标记语言,从语义上来说,它只是一种是一种标识性的语言,并不是一种编程语言。 <p>这是一段话</p>通过这个标签可以表示文本的一个段落。而且其中还有还有图片标签、视…...

02从零开始学Java之Java到底是个啥?
博主简介我是壹壹哥(孙玉昌),十年软件开发授课经验,CSDN博客专家、阿里云专家博主、掘金优秀创作者、infoQ专家博主;关注壹壹哥(孙玉昌),带你玩转Java,轻松实现从入门到放弃,哦不,到熟悉&#x…...

KEIL5中头文件路劲包含问题
方式1:1.Keil中添加头文件相对路劲的方法在c/c配置中添加路劲,最终是将添加的绝对路径转化为相对路径;注意:相对路径的当前位置指.uvproj文件所在位置在C/C配置中的include paths”中添加工程所用的所有头文件的路径;2…...

机智云目前我用过最便捷的物联网快速开发方案
GE211 MINI DTU上手来看,是一款尺寸比较小巧的模块,适合放置在几乎所有白色家电中,通过ph2.0端子(注意不要买错)引出了5v、gnd、tx、rx。可以说是非常方便了。下面正式开始我们的接入流程:首先注册一个机智…...

MySQL基础篇1
第1章 数据库介绍 1.1 数据库概述 什么是数据库? 数据库就是存储数据的仓库,其本质是一个文件系统,数据按照特定的格式将数据存储起来,用户可以对数据库中的数据进行增加,修改,删除及查询操作。 数据库分两…...

XML Group端口详解
在XML数据映射过程中,经常需要对数据进行分组聚合操作。例如,当处理包含多个物料明细的XML文件时,可能需要将相同物料号的明细归为一组,或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码,增加了开…...
Python爬虫实战:研究MechanicalSoup库相关技术
一、MechanicalSoup 库概述 1.1 库简介 MechanicalSoup 是一个 Python 库,专为自动化交互网站而设计。它结合了 requests 的 HTTP 请求能力和 BeautifulSoup 的 HTML 解析能力,提供了直观的 API,让我们可以像人类用户一样浏览网页、填写表单和提交请求。 1.2 主要功能特点…...

观成科技:隐蔽隧道工具Ligolo-ng加密流量分析
1.工具介绍 Ligolo-ng是一款由go编写的高效隧道工具,该工具基于TUN接口实现其功能,利用反向TCP/TLS连接建立一条隐蔽的通信信道,支持使用Let’s Encrypt自动生成证书。Ligolo-ng的通信隐蔽性体现在其支持多种连接方式,适应复杂网…...

微信小程序之bind和catch
这两个呢,都是绑定事件用的,具体使用有些小区别。 官方文档: 事件冒泡处理不同 bind:绑定的事件会向上冒泡,即触发当前组件的事件后,还会继续触发父组件的相同事件。例如,有一个子视图绑定了b…...
java调用dll出现unsatisfiedLinkError以及JNA和JNI的区别
UnsatisfiedLinkError 在对接硬件设备中,我们会遇到使用 java 调用 dll文件 的情况,此时大概率出现UnsatisfiedLinkError链接错误,原因可能有如下几种 类名错误包名错误方法名参数错误使用 JNI 协议调用,结果 dll 未实现 JNI 协…...

基于Docker Compose部署Java微服务项目
一. 创建根项目 根项目(父项目)主要用于依赖管理 一些需要注意的点: 打包方式需要为 pom<modules>里需要注册子模块不要引入maven的打包插件,否则打包时会出问题 <?xml version"1.0" encoding"UTF-8…...
vue3 定时器-定义全局方法 vue+ts
1.创建ts文件 路径:src/utils/timer.ts 完整代码: import { onUnmounted } from vuetype TimerCallback (...args: any[]) > voidexport function useGlobalTimer() {const timers: Map<number, NodeJS.Timeout> new Map()// 创建定时器con…...
在鸿蒙HarmonyOS 5中使用DevEco Studio实现录音机应用
1. 项目配置与权限设置 1.1 配置module.json5 {"module": {"requestPermissions": [{"name": "ohos.permission.MICROPHONE","reason": "录音需要麦克风权限"},{"name": "ohos.permission.WRITE…...
精益数据分析(97/126):邮件营销与用户参与度的关键指标优化指南
精益数据分析(97/126):邮件营销与用户参与度的关键指标优化指南 在数字化营销时代,邮件列表效度、用户参与度和网站性能等指标往往决定着创业公司的增长成败。今天,我们将深入解析邮件打开率、网站可用性、页面参与时…...

深度学习习题2
1.如果增加神经网络的宽度,精确度会增加到一个特定阈值后,便开始降低。造成这一现象的可能原因是什么? A、即使增加卷积核的数量,只有少部分的核会被用作预测 B、当卷积核数量增加时,神经网络的预测能力会降低 C、当卷…...