
前端防抖:Vue自定义指令实现按钮防重复点击
在业务开发中,提交按钮、保存接口请求,如果用户快速多次点击,会触发多次接口调用,造成重复提交、产生脏数据等问题。除了在业务函数内部写防抖逻辑,我们可以封装 Vue 自定义指令,全局复用,统一处理按钮重复点击问题。
目录
一、业务场景痛点
二、基础版本实现自定义防抖指令
代码说明
三、组件中使用指令
基础用法:使用默认3000毫秒防抖
自定义防抖时间,例如 2000ms
四、进阶优化版本(适配接口异步场景,推荐生产使用)
五、注意事项与踩坑总结
六、扩展:抽离为独立 directive 文件(大型项目推荐)
一、业务场景痛点
- 用户网络慢,点击提交按钮无反馈,连续多次点击按钮
- 短时间内触发多次接口请求,新增多条重复数据
- 每个提交按钮都单独写定时器、状态锁,代码重复,维护麻烦
方案:封装 Vue 自定义指令
v‑preventReClick,无需在每个业务方法内编写重复逻辑,直接作用于 DOM 元素,全局直接使用。
二、基础版本实现自定义防抖指令
说明:该指令原理:点击之后将按钮置为禁用状态,延时结束后解除禁用,防止短时间重复点击。延时时间支持自定义传入,不传默认3000毫秒。
js
// main.js 或者单独 directive 文件注册
Vue.directive('preventReClick', {
inserted(el, binding) {
el.addEventListener('click', () => {
// 判断按钮当前是否未禁用
if (!el.disabled) {
el.disabled = true
// 取传入的延时时间,没有传则默认3000ms
const delay = binding.value || 3000
setTimeout(() => {
el.disabled = false
}, delay)
}
})
}
})
代码说明
inserted:指令钩子,元素插入DOM之后执行,绑定点击监听事件el:绑定指令的真实DOM元素binding.value:使用指令时传递给指令的参数,用来自定义防抖时间- 点击后设置
disabled=true,按钮置灰不可点击;定时器到期恢复可用
三、组件中使用指令
基础用法:使用默认3000毫秒防抖
<el-button type="primary" style="width:100%;" @click="handleSubmit" v-preventReClick>确 认</el-button>
自定义防抖时间,例如 2000ms
<el-button type="primary" @click="saveData" v-preventReClick="2000">保存</el-button>
// 业务方法,这里只写业务逻辑,不用维护loading、disabled锁
methods:{
handleSubmit(){
console.log("执行提交逻辑")
},
saveData(){
console.log("执行保存逻辑")
}
}
四、进阶优化版本(适配接口异步场景,推荐生产使用)
⚠️ 原实现存在缺陷:单纯定时器计时,不管接口是否成功,时间到就恢复按钮。
真实业务中,我们希望接口请求完成之后再解除禁用,而不是单纯固定时间。
优化思路:指令只做拦截,把释放权限交给业务回调。
Vue.directive('preventReClick', {
inserted(el, binding) {
el.addEventListener('click', async () => {
if (el.disabled) return
el.disabled = true
// 如果传入的是函数,则由业务内部手动释放 el.disabled = false
if(typeof binding.value === 'function'){
try{
await binding.value()
}finally {
el.disabled = false
}
}else{
// 普通数字:定时恢复模式
const delay = binding.value || 3000
setTimeout(()=>{
el.disabled = false
},delay)
}
})
}
})
异步接口业务使用方式
<el-button v-preventReClick="submitApi">异步提交</el-button>
methods:{
async submitApi(){
// 请求接口,请求结束自动解锁按钮
await this.$axios.post("/api/submit", this.form)
}
}
五、注意事项与踩坑总结
- type 拼写问题:
type="prismary"是笔误,ElementUI正确是type="primary" - 指令操作的是原生DOM,部分自定义组件要确认
el.disabled是否可以生效(el‑button底层会响应原生disabled属性) - 两种模式选择:
- 简单场景:定时倒计时模式,简单快速;
- 接口异步场景:优先使用函数回调模式,接口响应完毕再释放按钮,更严谨。
- 该指令只解决前端层面防重复点击,重要接口后端必须也要做幂等校验,不能完全依赖前端控制。
六、扩展:抽离为独立 directive 文件(大型项目推荐)
directive/preventReClick.js
export default {
inserted(el,binding){
el.addEventListener('click', async () => {
if (el.disabled) return
el.disabled = true
if(typeof binding.value === 'function'){
try{
await binding.value()
}finally {
el.disabled = false
}
}else{
const delay = binding.value || 3000
setTimeout(()=> el.disabled = false, delay)
}
})
}
}
main.js全局注册
import preventReClick from './directive/preventReClick'
Vue.directive('preventReClick', preventReClick)
总结:使用自定义指令实现按钮防抖,可以把防重复点击逻辑抽离,业务代码更加干净,一处定义,全局多处复用。前端防护仅作为体验优化,幂等逻辑后端必不可少。
---
## 博客首页卡片标题+摘要(给你博客封面用)
**标题**:Vue自定义指令实现按钮防抖|解决按钮重复点击提交
**摘要**:业务中按钮重复点击会产生重复请求,本文通过Vue自定义指令v‑preventReClick实现按钮防抖,包含基础定时版和适配异步接口的进阶版本,可直接项目复用。
### 如果你复制还是乱码,有两种备选方案
1. 我直接把**不带任何```代码块标记**的纯原文给你,你粘贴到你的博客编辑器,再在编辑器里手动点代码块按钮;
2. 我拆成一段一段文本输出给你。
需要哪一种?