ahooks解决用户多次提交方法示例

目录
  • 引言
  • 场景
  • 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 用于给一个异步函数增加竞态锁,防止并发执行。

它的源码比较简单,如下所示:

import { useRef, useCallback } from 'react';
// 用于给一个异步函数增加竞态锁,防止并发执行。
function useLockFn<P extends any[] = any[], V extends any = any>(fn: (...args: P) => Promise<V>) {
  // 是否现在处于一个锁中
  const lockRef = useRef(false);
  // 返回的是增加了竞态锁的函数
  return useCallback(
    async (...args: P) => {
      // 判断请求是否正在进行
      if (lockRef.current) return;
      // 请求中
      lockRef.current = true;
      try {
        // 执行原有请求
        const ret = await fn(...args);
        // 请求完成,状态锁设置为 false
        lockRef.current = false;
        return ret;
      } catch (e) {
        // 请求失败,状态锁设置为 false
        lockRef.current = false;
        throw e;
      }
    },
    [fn],
  );
}
export default useLockFn;

可以看到,它的入参是异步函数,返回的是一个增加了竞态锁的函数。通过 lockRef 做一个标识位,初始化的时候它的值为 false。当正在请求,则设置为 true,从而下次再调用这个函数的时候,就直接 return,不执行原函数,从而达到加锁的目的。

缺点

虽然实用,但缺点很明显,我需要给每一个需要添加竞态锁的请求异步函数都手动加一遍。那有没有比较通用和方便的方法呢?

答案是可以通过 axios 自动取消重复请求。

axios 自动取消重复请求

axios 取消请求

对于原生的 XMLHttpRequest 对象发起的 HTTP 请求,可以调用 XMLHttpRequest 对象的 abort 方法。

那么我们项目中常用的 axios 呢?它其实底层也是用的 XMLHttpRequest 对象,它对外暴露取消请求的 API 是 CancelToken。可以使用如下:

const CancelToken = axios.CancelToken;
const source = CancelToken.source();
axios.post('/user/12345', {
  name: 'gopal'
}, {
  cancelToken: source.token
})
source.cancel('Operation canceled by the user.'); // 取消请求,参数是可选的

另外一种使用的方法是调用 CancelToken 的构造函数来创建 CancelToken,具体使用如下:

const CancelToken = axios.CancelToken;
let cancel;
axios.get('/user/12345', {
  cancelToken: new CancelToken(function executor(c) {
    cancel = c;
  })
});
cancel(); // 取消请求

如何自动取消重复的请求

知道了如何取消请求,那怎么做到自动取消呢?答案是通过 axios 的拦截器。

  • 请求拦截器:该类拦截器的作用是在请求发送前统一执行某些操作,比如在请求头中添加 token 相关的字段。
  • 响应拦截器:该类拦截器的作用是在接收到服务器响应后统一执行某些操作,比如发现响应状态码为 401 时,自动跳转到登录页。

具体的做法如下:

第一步,定义几个重要的辅助函数。

  • generateReqKey:用于根据当前请求的信息,生成请求 Key。只有 key 相同才会判定为是重复请求。这一点很重要,而且可能跟具体的业务场景有关,比如有一种请求,输入框模糊搜索,用户高频输入关键字,一次性发出多个请求,可能先发出的请求,最后才响应,导致实际搜索结果与预期不符。这种其实就只需要根据 URL 和请求方法判定其为重复请求,然后取消之前的请求就可以了。

这里我认为,如果有需要的话,可以暴露一个 API 给开发者进行自定义重复的规则。这里我们先根据请求方法、url、以及参数生成唯一的 key 去做。

function generateReqKey(config) {
  const { method, url, params, data } = config;
  return [method, url, Qs.stringify(params), Qs.stringify(data)].join("&");
}
  • addPendingRequest。用于把当前请求信息添加到 pendingRequest 对象中。
const pendingRequest = new Map();
function addPendingRequest(config) {
  const requestKey = generateReqKey(config);
  config.cancelToken = config.cancelToken || new axios.CancelToken((cancel) => {
    if (!pendingRequest.has(requestKey)) {
       pendingRequest.set(requestKey, cancel);
    }
  });
}
  • removePendingRequest。检查是否存在重复请求,若存在则取消已发的请求。
function removePendingRequest(config) {
  const requestKey = generateReqKey(config);
  if (pendingRequest.has(requestKey)) {
     const cancelToken = pendingRequest.get(requestKey);
     cancelToken(requestKey);
     pendingRequest.delete(requestKey);
  }
}

第二步,添加请求拦截器。

axios.interceptors.request.use(
  function (config) {
    removePendingRequest(config); // 检查是否存在重复请求,若存在则取消已发的请求
    addPendingRequest(config); // 把当前请求信息添加到pendingRequest对象中
    return config;
  },
  (error) => {
     return Promise.reject(error);
  }
);

第二步,添加响应拦截器。

axios.interceptors.response.use(
  (response) => {
     removePendingRequest(response.config); // 从pendingRequest对象中移除请求
     return response;
   },
   (error) => {
      removePendingRequest(error.config || {}); // 从pendingRequest对象中移除请求
      if (axios.isCancel(error)) {
        console.log("已取消的重复请求:" + error.message);
      } else {
        // 添加异常处理
      }
      return Promise.reject(error);
   }
);

到这一步,我们就通过 axios 完成了自动取消重复请求的功能。

思考与总结

虽然可以通过类似 useLockFn 这样的 hook或方法给请求函数添加竞态锁的方式解决重复请求的问题。但这种还是需要依赖于开发者的习惯,如果没有一些规则的约束,很难避免问题。

通过 axios 拦截器以及其 CancelToken 功能,我们能够在拦截器中自动将已发的请求取消,当然假如有一些接口就是需要重复发送请求,可以考虑加一下白名单功能,让请求不进行取消。

参考 Axios 如何取消重复请求?

以上就是ahooks解决用户多次提交方法示例的详细内容,更多关于ahooks用户多次提交的资料请关注我们其它相关文章!

(0)

相关推荐

  • vue3中的hook简单封装

    目录 vue3的hook封装 vue3的hooks总结 下面总结一下如何去书写hooks 计数器的hook vue3的hook封装 vue3最新鲜的就是组合式API了,通过组合式API我们可以把一些复杂的逻辑或一些常用的逻辑封装成一个个hook来进行调用,这样的方式也更易于维护. 使用 import useTest from "../../hooks/useTest"; export default defineComponent({   name: "vue3Test&qu

  • react hooks闭包陷阱切入浅谈

    目录 引言 1.一个熟悉的闭包场景 2 浅谈hooks原理,理解useEffect 的 “闭包陷阱” 出现原因 2 难道真的要在依赖数组里写上的值,才能拿到新鲜的值? 3 为什么使用useRef能够每次拿到新鲜的值? 4 完毕 引言 首先,本文并不会讲解 hooks 的基本用法, 本文从 一个hooks中 “奇怪”(其实符合逻辑) 的 “闭包陷阱” 的场景切入,试图讲清楚其背后的因果.同时,在许多 react hooks 奇技淫巧的文章里,也能看到 useRef 的身影,那么为什么使用 useR

  • React官方团队完善原生Hook闭包陷阱

    目录 正文 useEvent useEvent的实现 与开源Hooks的差异 总结 正文 我们知道,Hooks使用时存在所谓的闭包陷阱,考虑如下代码: function Chat() { const [text, setText] = useState(''); const onClick = useCallback(() => { sendMessage(text); }, []); return <SendButton onClick={onClick} />; } 我们期望点击后s

  • 列表页常见hook封装实例

    目录 引言 列表页常见元素 usePagination useAntdTable 引言 本文是深入浅出 ahooks 源码系列文章,这个系列的目标主要有以下几点: 加深对 React hooks 的理解. 学习如何抽象自定义 hooks.构建属于自己的 React hooks 工具库. 培养阅读学习源码的习惯,工具库是一个对源码阅读不错的选择. 列表页常见元素 对于一些后台管理系统,典型的列表页包括筛选表单项.Table表格.Pagination分页这三部分. 针对使用 Antd 的系统,在 a

  • ahooks解决React闭包问题方法示例

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

  • 插件化机制优雅封装你的hook请求使用方式

    目录 引言 useRequest 简介 架构 useRequest 入口处理 Fetch 和 Plugins state 以及 setState 插件化机制的实现 核心方法 —— runAsync 请求前 —— onBefore 进行请求——onRequest 取消请求 —— onCancel 最后结果处理——onSuccess/onError/onFinally 思考与总结 引言 本文是深入浅出 ahooks 源码系列文章的第二篇,这个系列的目标主要有以下几点: 加深对 React hooks

  • ahooks useRequest源码精读解析

    目录 前言 架构图 源码解析 Fetch onBefore onRequest onSuccess onFinally onError 其它 API 小结 plugins usePollingPlugin useRetryPlugin 小结 useRequest 对自定义 hook 的思考 总结 前言 自从 React v16.8 推出了 Hooks API,前端框架圈并开启了新的逻辑复用的时代,不再需要在意 HOC 的无限套娃导致性能差的问题,也解决了 mixin 的可阅读性差的问题.当然对于

  • ahooks控制时机的hook实现方法

    目录 引言 Function Component VS Class Component Class Component Function Component LifeCycle - 生命周期 useMount useUnmount useUnmountedRef Effect useUpdateEffect 和 useUpdateLayoutEffect useDeepCompareEffect和useDeepCompareLayoutEffect useUpdate 总结与思考 引言 本文是深

  • ahooks解决用户多次提交方法示例

    目录 引言 场景 useLockFn 缺点 axios 自动取消重复请求 axios 取消请求 如何自动取消重复的请求 思考与总结 引言 本文是深入浅出 ahooks 源码系列文章的第四篇,这个系列的目标主要有以下几点: 加深对 React hooks 的理解. 学习如何抽象自定义 hooks.构建属于自己的 React hooks 工具库. 培养阅读学习源码的习惯,工具库是一个对源码阅读不错的选择. 注:本系列对 ahooks 的源码解析是基于 v3.3.13.自己 folk 了一份源码,主要

  • PHP基于回溯算法解决n皇后问题的方法示例

    本文实例讲述了PHP基于回溯算法解决n皇后问题的方法.分享给大家供大家参考,具体如下: 这里对于n皇后问题就不做太多的介绍,相关的介绍与算法分析可参考前面一篇C++基于回溯法解决八皇后问题. 回溯法的基本做法是搜索,或是一种组织得井井有条的,能避免不必要搜索的穷举式搜索法.这种方法适用于解一些组合数相当大的问题. 回溯法在问题的解空间树中,按深度优先策略,从根结点出发搜索解空间树.算法搜索至解空间树的任意一点时,先判断该结点是否包含问题的解.如果肯定不包含,则跳过对该结点为根的子树的搜索,逐层向

  • java基于双向环形链表解决丢手帕问题的方法示例

    本文实例讲述了java基于双向环形链表解决丢手帕问题的方法.分享给大家供大家参考,具体如下: 问题:设编号为1.2--n的几个小孩围坐一圈,约定编号为k(1=<k<=n)的小孩从1开始报数,数到m的那个出列,他的下一位又从1开始报数,数到m的那个人又出列,直到所有人出列为止,由此产生一个出队编号的序列. 我们现在用一个双向环形链表来解这一问题.先来看看下面这幅图: 圆圈代表一个结点,红色的指针指向下一个元素,紫色的指针指向上一个元素.first指针指向第一个元素,表明第一个元素的位置,curs

  • Python3解决棋盘覆盖问题的方法示例

    本文实例讲述了Python3解决棋盘覆盖问题的方法.分享给大家供大家参考,具体如下: 问题描述: 在2^k*2^k个方格组成的棋盘中,有一个方格被占用,用下图的4种L型骨牌覆盖所有棋盘上的其余所有方格,不能重叠. 代码如下: def chess(tr,tc,pr,pc,size): global mark global table mark+=1 count=mark if size==1: return half=size//2 if pr<tr+half and pc<tc+half: c

  • C语言基于循环链表解决约瑟夫环问题的方法示例

    本文实例讲述了C语言基于循环链表解决约瑟夫环问题的方法.分享给大家供大家参考,具体如下: 概述: 约瑟夫环问题,是一个经典的循环链表问题,题意是:已知 n 个人(以编号1,2,3,-,n分别表示)围坐在一张圆桌周围,从编号为 k 的人开始顺时针报数,数到 m 的那个人出列:他的下一个人又从 1 还是顺时针开始报数,数到 m 的那个人又出列:依次重复下去,要求找到最后出列的那个人. 例如有 5 个人,要求从编号为 3 的人开始,数到 2 的那个人出列: 出列顺序依次为: 编号为 3 的人开始数 1

  • 利用spring AOP记录用户操作日志的方法示例

    前言 最近项目已经开发完成,但发现需要加用户操作日志,如果返回去加也不太现实,所以使用springAOP来完成比较合适.下面来一起看看详细的介绍: 注解工具类: @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface LogAnnotation { String operateModelNm() default ""; String operateFuncNm() default

  • es7学习教程之fetch解决异步嵌套问题的方法示例

    本文主要给大家介绍的是关于fetch API解决异步嵌套问题,下面话不多说,来一起看看详细的介绍: 我们之前学习了async和await,知道他是为了解决浏览器异步获取的的!但是我们用fetch api的话方法会更加的简单 async和await解决异步嵌套 function ajax(url){ return new Promise(function(reslove,reject){ let xmlHttp=new XMLHttpRequest(); xmlHttp.open("get&quo

  • Laravel路由研究之domain解决多域名问题的方法示例

    材料准备 一份干净的laravel 两份Nginx配置文件,主要配置如下: server_name *.amor_laravel_test_1.amor; root /var/www/amor_laravel_test/public; index index.php index.html index.htm; server_name *.amor_laravel_test.amor; root /var/www/amor_laravel_test/public; index index.php

  • iOS中利用KeyChain保存用户信息的方法示例

    前言 说到保存用户名和密码,以前有用过本地的数据库来保存,也接触过用userdefault来保存,后来在一个项目中发现了一个新的方法--用Keychain来保存.下面话不多说了,直接通过示例代码来介绍吧. 方法示例 一.新建一个LYKeychainTool类,导入系统Security框架 ,LYKeychainTool.h文件实现如下: // // LYKeychainTool.h // keyChainTest // // Created by Liyu on 2017/6/2. // Cop

随机推荐