方案一
封装为 Hooks 函数
> 在一个项目中,肯定不止一处需要给按钮做防止重复点击按钮的操作,这里封装一个等接口加载完之后,才能进行 下一次点击的hooks函数避免造成代码冗余
ts
//useLoading.ts
import { ref, type Ref } from "vue";
/**
* 封装一个带有 loading 状态的 Hook,防止重复点击
* @returns { isLoading: Ref<boolean>, withLoading: (fn: () => Promise<any>) => Promise<void> }
*/
export function useLoading() {
const isLoading: Ref<boolean> = ref(false);
/**
* 包装异步函数,自动管理 loading 状态
* @param fn 需要执行的异步函数
*/
const withLoading = async (fn: () => Promise<any>): Promise<void> => {
isLoading.value = true;
try {
await fn();
} finally {
isLoading.value = false;
}
};
return {
isLoading,
withLoading,
};
}
//xxx.vue
<script setup lang="ts">
import axios from "axios";
import { useLoading } from "@/hooks/useLoading";
const { isLoading, withLoading } = useLoading();
const handleTestLoading = async () => {
await withLoading(async () => {
// 模拟请求数据
try {
const res = await axios.get(
"http://xx.xx.xx.xx:8080/api"
);
console.log(res.data);
} catch (err) {
console.error("请求失败", err);
}
});
};
</script>
<template>
<button @click="handleTestLoading" :disabled="isLoading">
{{ isLoading ? "加载中..." : "点击获取数据" }}
</button>
</div>
</template>
方案二
封装一个带防抖的自定义指令
js
// prevent-repeate-clicks.js
export default {
mounted(el, binding) {
const { value = 3000, arg = "disable" } = binding;
let timer = null;
const handleClick = () => {
if (arg === "disable") {
if (el.disabled) return;
el.disabled = true;
timer = setTimeout(() => {
el.disabled = false;
}, value);
} else if (arg === "debounce") {
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
binding.value?.();
}, value);
}
};
el._clickHandler = handleClick;
el.addEventListener("click", handleClick);
},
unmounted(el) {
if (el._clickHandler) {
el.removeEventListener("click", el._clickHandler);
}
},
};
//main.ts 全局使用
import preventRepeatedClick from "@/directive/prevent-repeate-clicks";
app.directive("prevent-repeated-click", preventRepeatedClick);
//xxx.vue
<template>
<button v-prevent-repeated-click="2000">2秒内禁用</button>
</template>