手写中实现并学习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 数据库概述 什么是数据库? 数据库就是存储数据的仓库,其本质是一个文件系统,数据按照特定的格式将数据存储起来,用户可以对数据库中的数据进行增加,修改,删除及查询操作。 数据库分两…...
Opencv中的addweighted函数
一.addweighted函数作用 addweighted()是OpenCV库中用于图像处理的函数,主要功能是将两个输入图像(尺寸和类型相同)按照指定的权重进行加权叠加(图像融合),并添加一个标量值&#x…...
linux arm系统烧录
1、打开瑞芯微程序 2、按住linux arm 的 recover按键 插入电源 3、当瑞芯微检测到有设备 4、松开recover按键 5、选择升级固件 6、点击固件选择本地刷机的linux arm 镜像 7、点击升级 (忘了有没有这步了 估计有) 刷机程序 和 镜像 就不提供了。要刷的时…...
在web-view 加载的本地及远程HTML中调用uniapp的API及网页和vue页面是如何通讯的?
uni-app 中 Web-view 与 Vue 页面的通讯机制详解 一、Web-view 简介 Web-view 是 uni-app 提供的一个重要组件,用于在原生应用中加载 HTML 页面: 支持加载本地 HTML 文件支持加载远程 HTML 页面实现 Web 与原生的双向通讯可用于嵌入第三方网页或 H5 应…...
使用Matplotlib创建炫酷的3D散点图:数据可视化的新维度
文章目录 基础实现代码代码解析进阶技巧1. 自定义点的大小和颜色2. 添加图例和样式美化3. 真实数据应用示例实用技巧与注意事项完整示例(带样式)应用场景在数据科学和可视化领域,三维图形能为我们提供更丰富的数据洞察。本文将手把手教你如何使用Python的Matplotlib库创建引…...
省略号和可变参数模板
本文主要介绍如何展开可变参数的参数包 1.C语言的va_list展开可变参数 #include <iostream> #include <cstdarg>void printNumbers(int count, ...) {// 声明va_list类型的变量va_list args;// 使用va_start将可变参数写入变量argsva_start(args, count);for (in…...
在 Spring Boot 中使用 JSP
jsp? 好多年没用了。重新整一下 还费了点时间,记录一下。 项目结构: pom: <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://ww…...
【Linux手册】探秘系统世界:从用户交互到硬件底层的全链路工作之旅
目录 前言 操作系统与驱动程序 是什么,为什么 怎么做 system call 用户操作接口 总结 前言 日常生活中,我们在使用电子设备时,我们所输入执行的每一条指令最终大多都会作用到硬件上,比如下载一款软件最终会下载到硬盘上&am…...
Vue 模板语句的数据来源
🧩 Vue 模板语句的数据来源:全方位解析 Vue 模板(<template> 部分)中的表达式、指令绑定(如 v-bind, v-on)和插值({{ }})都在一个特定的作用域内求值。这个作用域由当前 组件…...
flow_controllers
关键点: 流控制器类型: 同步(Sync):发布操作会阻塞,直到数据被确认发送。异步(Async):发布操作非阻塞,数据发送由后台线程处理。纯同步(PureSync…...
C++ 类基础:封装、继承、多态与多线程模板实现
前言 C 是一门强大的面向对象编程语言,而类(Class)作为其核心特性之一,是理解和使用 C 的关键。本文将深入探讨 C 类的基本特性,包括封装、继承和多态,同时讨论类中的权限控制,并展示如何使用类…...
