vue 如何做按钮防重复提交 并有几种方案

2,266 阅读3分钟

vue 如何做防重复提交,并有几种方案

前言:按钮防重复最常见的场景的需求最主要的就是用户重复点击的时候不重复请求接口给后端,为了优化用户的体验我们更多的时候会加上loading展示,在接口返回较慢时缓解用户厌倦感,需求简单,但是对于前端来实现的方案有很多种,出此文章罗列一下。

基于axios

原理

使用axios作为接口请求工具时,请求前的config参数自带[cancelToken]方法,开发者可以手动取消该接口发送至后端,在这个基础上,我们可以定义一个map数组,a接口每次请求前加入缓存数组,在请求完后delete掉保存在数组的a接口请求数据,如果在a接口未返回数据时再遇到a接口请求,判断为重复请求,则调用[cancelToken] 请求方法打断该接口。

该方法作用全局,并不仅仅限制于按钮,如果有接口需求特殊,需要重复请求,可以另设白名单,避免请求被限制。

代码

定义公告文件 userRequireStop.ts


import axios, { AxiosPromise, AxiosRequestConfig, AxiosResponse } from "axios";
import { watch, reactive , onMounted } from "vue";
/**
* @description 添加请求信息 **/
const apiWhitelist = ['/base/queryCity','/base/queryProvince'];//接口白名单,允许重复请求
export default function userRequireStop (){
  let pendingRequest = reactive(new Map());
  function getRequestKey(config) {
    let {
        method,
        url,
        params,
        data,
        acRequestType
    } = config;
    // axios中取消请求及阻止重复请求的方法
    // 参数相同时阻止重复请求:
    // return [method, url, JSON.stringify(params), JSON.stringify(data)].join("&");
    // 请求方法相同,参数不同时阻止重复请求
    return [method, url,acRequestType ? acRequestType : 'default'].join("&");
  }
  function addPendingRequest(config) {
    // console.log(config.url)
    let requestKey = getRequestKey(config);
    config.cancelToken = config.cancelToken || new axios.CancelToken((cancel) => {
        if (!pendingRequest.has(requestKey)) {
            pendingRequest.set(requestKey, cancel);
            // vuex.commit('setPendingRequest',{requestKey, cancel})
        }
    });
  }
  /**
  * @description 取消重复请求 **/
   function removePendingRequest(config) {
    let requestKey = getRequestKey(config);
    if (pendingRequest.has(requestKey) && !apiWhitelist.some(item => requestKey.includes(item))) {
        // 如果是重复的请求,则执行对应的cancel函数
        let cancel = pendingRequest.get(requestKey);
        cancel('CancelToken');
        // 将前一次重复的请求移除
        pendingRequest.delete(requestKey);
        // vuex.commit('deletePendingRequest',requestKey)
    }
  }
  return {
    removePendingRequest,
    addPendingRequest
  }
}

引入在axios定义请求的公共文件

//请求与接口前
request.interceptors.request.use(
  async (config: AxiosRequestConfig & { cType?: boolean }) => {
      .....
     // 检查是否存在重复请求,若存在则取消已发的请求
     removePendingRequest(config);
     // 把当前请求信息添加到pendingRequest对象中
     addPendingRequest(config);
     .....
     return config;
  }
);

//请求接口返回
request.interceptors.response.use(
  async (response: AxiosResponse) => {
    .....
    removePendingRequest(response.config);
    .....
    return response;
  }
  /* error => {} */ // 已在 export default catch
);

评价

该实现只是做了全局的防重复,对于项目的所有接口去使用,但是,对于需要给按钮增加loading的时候,还需要单独去做交互效果。

基于二次封装按钮组件回调

原理,二次封装组件 点击抛出回调,定义回调形参a,接口请求时传递true,打开loading,接口返回后形参传递false,关闭true。

代码

//伪代码
<template>
    <t-buttom @ok="onOk">按钮</t-buttom>
</template>
<script setup>
    const onOk =  async function(cb){
        cb(true);
        await 接口();
        cb(false);
    }
</srcipt>

评价

实现简单,二次封装的效果避免在业余组件中去取定义多余变量,仅仅是只针对按钮去做响应的事情,防重复和loading在形参的控制中一并实现。

使用自定义指令去做对应的防重复

原理:定义自定义指令,在bing绑定使用节流结束时间控制是否可点击,使用addEventListener 控制点击。

代码

Vue.directive('throttle', {
  bind: (el, binding) => {
    let throttleTime = binding.value; // 节流时间
    if (!throttleTime) { // 用户若不设置节流时间,则默认2s
      throttleTime = 2000;
    }
    let cbFun;
    el.addEventListener('click', event => {
      if (!cbFun) { // 第一次执行
        cbFun = setTimeout(() => {
          cbFun = null;
        }, throttleTime);
      } else {
        event && event.stopImmediatePropagation();
      }
    }, true);
  },
});
// 2.为button标签设置v-throttle自定义指令
<button @click="sayHello" v-throttle>提交</button>

评价

通过指定时间控制是否反映用户的点击操作,适用于对时间精度不高的场景。

总结

以上三种方法主要还是实际项目场景,使用那种方法实现可高复用 易于控制 的防重复还是看开发者的选择。有更多方法可在评论区讨论。