[前端] ahooks解决用户多次提交方法示例

2100 0
王子 2022-10-21 15:42:48 | 显示全部楼层 |阅读模式
目录

    引言场景useLockFn
      缺点
    axios 自动取消重复请求
      axios 取消请求如何自动取消重复的请求
    思考与总结


引言

本文是深入浅出 ahooks 源码系列文章的第四篇,这个系列的目标主要有以下几点:
    加深对 React hooks 的理解。学习如何抽象自定义 hooks。构建属于自己的 React hooks 工具库。培养阅读学习源码的习惯,工具库是一个对源码阅读不错的选择。
注:本系列对 ahooks 的源码解析是基于 v3.3.13。自己 folk 了一份源码,主要是对源码做了一些解读,可见 详情。
系列文章:
    大家都能看得懂的源码(一)ahooks 整体架构篇如何使用插件化机制优雅的封装你的请求hook ahooks 是怎么解决 React 的闭包问题的?
本文来探索一下 ahooks 的 useLockFn。

场景

试想一下,有这么一个场景,有一个表单,你可能多次提交,就很可能导致结果不正确。
解决这类问题的方法有很多,比如添加 loading,在第一次点击之后就无法再次点击。另外一种方法就是给请求异步函数添加上一个静态锁,防止并发产生。这就是 ahooks 的 useLockFn 做的事情。

useLockFn

useLockFn 用于给一个异步函数增加竞态锁,防止并发执行。
它的源码比较简单,如下所示:
  1. import { useRef, useCallback } from 'react';
  2. // 用于给一个异步函数增加竞态锁,防止并发执行。
  3. function useLockFn<P extends any[] = any[], V extends any = any>(fn: (...args: P) => Promise<V>) {
  4.   // 是否现在处于一个锁中
  5.   const lockRef = useRef(false);
  6.   // 返回的是增加了竞态锁的函数
  7.   return useCallback(
  8.     async (...args: P) => {
  9.       // 判断请求是否正在进行
  10.       if (lockRef.current) return;
  11.       // 请求中
  12.       lockRef.current = true;
  13.       try {
  14.         // 执行原有请求
  15.         const ret = await fn(...args);
  16.         // 请求完成,状态锁设置为 false
  17.         lockRef.current = false;
  18.         return ret;
  19.       } catch (e) {
  20.         // 请求失败,状态锁设置为 false
  21.         lockRef.current = false;
  22.         throw e;
  23.       }
  24.     },
  25.     [fn],
  26.   );
  27. }
  28. export default useLockFn;
复制代码
可以看到,它的入参是异步函数,返回的是一个增加了竞态锁的函数。通过 lockRef 做一个标识位,初始化的时候它的值为 false。当正在请求,则设置为 true,从而下次再调用这个函数的时候,就直接 return,不执行原函数,从而达到加锁的目的。

缺点

虽然实用,但缺点很明显,我需要给每一个需要添加竞态锁的请求异步函数都手动加一遍。那有没有比较通用和方便的方法呢?
答案是可以通过 axios 自动取消重复请求。

axios 自动取消重复请求


axios 取消请求

对于原生的 XMLHttpRequest 对象发起的 HTTP 请求,可以调用 XMLHttpRequest 对象的 abort 方法。
那么我们项目中常用的 axios 呢?它其实底层也是用的 XMLHttpRequest 对象,它对外暴露取消请求的 API 是 CancelToken。可以使用如下:
  1. const CancelToken = axios.CancelToken;
  2. const source = CancelToken.source();
  3. axios.post('/user/12345', {
  4.   name: 'gopal'
  5. }, {
  6.   cancelToken: source.token
  7. })
  8. source.cancel('Operation canceled by the user.'); // 取消请求,参数是可选的
复制代码
另外一种使用的方法是调用 CancelToken 的构造函数来创建 CancelToken,具体使用如下:
  1. const CancelToken = axios.CancelToken;
  2. let cancel;
  3. axios.get('/user/12345', {
  4.   cancelToken: new CancelToken(function executor(c) {
  5.     cancel = c;
  6.   })
  7. });
  8. cancel(); // 取消请求
复制代码
如何自动取消重复的请求

知道了如何取消请求,那怎么做到自动取消呢?答案是通过 axios 的拦截器。
    请求拦截器:该类拦截器的作用是在请求发送前统一执行某些操作,比如在请求头中添加 token 相关的字段。响应拦截器:该类拦截器的作用是在接收到服务器响应后统一执行某些操作,比如发现响应状态码为 401 时,自动跳转到登录页。
具体的做法如下:
第一步,定义几个重要的辅助函数。
    generateReqKey:用于根据当前请求的信息,生成请求 Key。只有 key 相同才会判定为是重复请求。这一点很重要,而且可能跟具体的业务场景有关,比如有一种请求,输入框模糊搜索,用户高频输入关键字,一次性发出多个请求,可能先发出的请求,最后才响应,导致实际搜索结果与预期不符。这种其实就只需要根据 URL 和请求方法判定其为重复请求,然后取消之前的请求就可以了。
这里我认为,如果有需要的话,可以暴露一个 API 给开发者进行自定义重复的规则。这里我们先根据请求方法、url、以及参数生成唯一的 key 去做。
  1. function generateReqKey(config) {
  2.   const { method, url, params, data } = config;
  3.   return [method, url, Qs.stringify(params), Qs.stringify(data)].join("&");
  4. }
复制代码
    addPendingRequest。用于把当前请求信息添加到 pendingRequest 对象中。
  1. const pendingRequest = new Map();
  2. function addPendingRequest(config) {
  3.   const requestKey = generateReqKey(config);
  4.   config.cancelToken = config.cancelToken || new axios.CancelToken((cancel) => {
  5.     if (!pendingRequest.has(requestKey)) {
  6.        pendingRequest.set(requestKey, cancel);
  7.     }
  8.   });
  9. }
复制代码
    removePendingRequest。检查是否存在重复请求,若存在则取消已发的请求。
  1. function removePendingRequest(config) {
  2.   const requestKey = generateReqKey(config);
  3.   if (pendingRequest.has(requestKey)) {
  4.      const cancelToken = pendingRequest.get(requestKey);
  5.      cancelToken(requestKey);
  6.      pendingRequest.delete(requestKey);
  7.   }
  8. }
复制代码
第二步,添加请求拦截器。
  1. axios.interceptors.request.use(
  2.   function (config) {
  3.     removePendingRequest(config); // 检查是否存在重复请求,若存在则取消已发的请求
  4.     addPendingRequest(config); // 把当前请求信息添加到pendingRequest对象中
  5.     return config;
  6.   },
  7.   (error) => {
  8.      return Promise.reject(error);
  9.   }
  10. );
复制代码
第二步,添加响应拦截器。
  1. axios.interceptors.response.use(
  2.   (response) => {
  3.      removePendingRequest(response.config); // 从pendingRequest对象中移除请求
  4.      return response;
  5.    },
  6.    (error) => {
  7.       removePendingRequest(error.config || {}); // 从pendingRequest对象中移除请求
  8.       if (axios.isCancel(error)) {
  9.         console.log("已取消的重复请求:" + error.message);
  10.       } else {
  11.         // 添加异常处理
  12.       }
  13.       return Promise.reject(error);
  14.    }
  15. );
复制代码
到这一步,我们就通过 axios 完成了自动取消重复请求的功能。

思考与总结

虽然可以通过类似 useLockFn 这样的 hook或方法给请求函数添加竞态锁的方式解决重复请求的问题。但这种还是需要依赖于开发者的习惯,如果没有一些规则的约束,很难避免问题。
通过 axios 拦截器以及其 CancelToken 功能,我们能够在拦截器中自动将已发的请求取消,当然假如有一些接口就是需要重复发送请求,可以考虑加一下白名单功能,让请求不进行取消。
参考 Axios 如何取消重复请求?
以上就是ahooks解决用户多次提交方法示例的详细内容,更多关于ahooks用户多次提交的资料请关注中国红客联盟其它相关文章!
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

中国红客联盟公众号

联系站长QQ:5520533

admin@chnhonker.com
Copyright © 2001-2025 Discuz Team. Powered by Discuz! X3.5 ( 粤ICP备13060014号 )|天天打卡 本站已运行