自定义一个具有类似节流功能的按钮

121 阅读4分钟

一. 开发时遇见的问题

在开发管理系统的时候遇到了一个小问题,就是不管是原生的按钮还是element组件库提供的按钮都没有实现节流的功能。这样可能导致我们连续点击按钮时触发多次网络请求,造成创建多条数据的问题。

one.gif

从上述图片可以看出,连续点击按钮就会创建多条的量表数据。那么如何解决这个问题呢,我们可以让点击按钮创建量表的操作,在一段时间里只执行一次,这样既能够点击按钮后创建量表,也能让创建量表的操作不会因为点击的频繁而重复执行。很明显,这里我们得需要用到节流的思想去解决这个问题。

二. 节流

1.什么是节流?

什么是节流呢?在我的理解里节流就是在规定的时间内只执行一次函数。有点类似于打游戏时的游戏技能,假设技能的冷却时间是60s,如果你点击了释放技能的按钮,那么将会有60s的冷却时间,在这段60s的冷却时间再点击按钮是无法再次释放技能的。

当然节流也是分有情况的,像上述游戏释放技能的例子是只要一点击就会触发释放技能,然后再进入冷却时间。还有另外一种情况可能是先进入冷却时间,冷却时间一过再释放技能。这两种方案的区别是执行时机是什么时候。需要采取哪种模式需要根据我们的开发场景而定,像我们上述图片的案例就得采用第一种节流的思想去解决。

2. 节流的简单实现

// 节流函数,用于限制回调函数的触发频率
function throttle(fn, delay) {
  let hasSkill = true; // 是否有技能 设置为true是为了保证第一次点就能释放
  return function (...args) {
    if (hasSkill) {
      hasSkill= false // 如果有技能,就设置为false,就是技能冷却
      fn.apply(this, args); // 释放技能
      timer = setTimeout(() => {
        hasSkill = true; // 冷却时间结束后,技能恢复
      }, delay);
    }
  };
}

三. 节流按钮的两种实现方式

1. 封装成一个新的组件throttle-button

由于elementui和元素的button组件没有给我们提供节流的功能,我们可以对这个类型的按钮做一个封装,使其具有节流的功能。封装的过程中最主要的就是控制按钮点击发出的emit行为。
创建一个throttle-btn文件夹:

image.png

编写如下代码

<template>
  <el-button :type="buttonType" @click="throttleClick">
    <slot></slot>
  </el-button>
</template>

<script setup>
import { ref } from "vue";
const $emit = defineEmits(["click"]);

const props = defineProps({
  delayTime: {
    type: Number,
    default: 5000,
  },
  buttonType: {
    type: String,
    default: "primary",
  },
  text: {
    type: String,
    default: "按钮",
  },
});
// const disabled = ref(false);
const hasSkill = ref(true);

function throttleClick() {
  if (hasSkill.value) {
    hasSkill.value = false;
    $emit("click");
    setTimeout(() => {
      hasSkill.value = true;
    }, props.delayTime);
  }
}

// function handleThrottleClick() {
//   if (!disabled.value) {
//     disabled.value = true;
//     $emit("click");
//     setTimeout(() => {
//       disabled.value = false;
//     }, props.delayTime);
//   }
// }
</script>

<style></style>

在main.js文件中全局注册组件:

image.png

其实也可以使用button中的disabled属性去做节流的操作,就像上述我注释的代码操作就行。
接下来我们就可以将我们原来的点击按钮用我们封装的组件替换掉啦。

image.png

替换之后的效果:

two.gif
点击多次也只是创建一条量表。

2. 使用自定义指令

我们也可以设置一个全局的自定义指令,去监听执行按钮的click的事件。

// 节流函数,用于限制回调函数的触发频率
function throttle(fn, delay) {
  let hasSkill = true;
  return function (...args) {
    if (hasSkill) {
      hasSkill = false;
      fn.apply(this, args);
      setTimeout(() => {
        hasSkill = true;
      }, delay);
    }
  };
}

app.directive("throttle", {
  mounted(el, binding) {
    // value是方法名,arg是节流的间隔时间
    const { value, arg } = binding;
    const delay = arg ? parseInt(arg, 10) : 300; // 默认节流时间为 300ms
    const throttledFn = throttle(value, delay);
    // 保存一下这个方法 方便后续销毁
    el._throttledFn = throttledFn;
    // 监听元素的点击事件 外部不需要再次监听点击事件
    el.addEventListener("click", throttledFn);
  },
  unmounted(el) {
    // 去除监听,避免内存泄漏问题
    el.removeEventListener("click", el._throttledFn);
  },
});

这里需要注意的是Vue2到Vue3的自定义指令的实现方式有点不太一样,上述代码是采用了Vue3的,使用Vue2的话只需要将mounted和unmounted修改成bind和unbind就行。这个自定义指令本质的操作就是在元素被创建的时候监听这个元素的点击,也就是说在外部我们就不要给元素绑定一个click事件,只需要给这个指令传递一个事件就行。

image.png
这样也能完成跟封装组件一样的效果啦:

three.gif

记录一下自己开发中遇到的这个问题,其实最主要的还是要明白节流的思想,有错的地方请大佬指证。