@tap连点仍触发多次,因:disabled仅视觉禁用不阻事件,须用响应式issending守卫+请求生命周期锁+tapcount校验+ui遮罩四重防护。

uni-app 里 @tap 连点仍触发多次?别信 :disabled
在微信/支付宝小程序中,:disabled="isSending" 只控制按钮灰显,**不阻止 @tap 事件冒泡和执行**。用户快速连点时,DOM 更新延迟会导致多次进入方法体——这是最常被忽略的底层事实。
必须在方法入口加守卫,且不能依赖 UI 状态做逻辑判断:
-
isSending必须是响应式变量(data或ref),点击后立即设为true - 方法第一行写
if (this.isSending) return,不是等请求发出去再设 - 按钮仍要保留
:disabled,仅用于视觉反馈,别把它当防线
为什么节流(_.debounce)在提交类场景容易失效?
防抖适合搜索、输入联动等“以最后一次为准”的场景;但表单提交、验证码发送这类操作,**用户点下去那一刻就该有确定性响应**——如果点了三次才发一次,体验反而更差,还可能因超时导致重复提交。
节流也不合适:它按固定间隔放行,而网络请求耗时不可控。比如设 1500ms 节流,但请求卡在 2s 才返回,用户第 2 次点击就会绕过节流直接触发新请求。
真正需要的是「请求生命周期锁」:
- 锁在
request开始前就上(isSubmitting = true) - 锁在
complete后才释放(不是success或fail) - 避免用
setTimeout模拟锁,网络异常时会漏掉解锁
封装请求时 return 空 Promise 会导致页面卡死?
常见错误是在请求拦截里检测到重复请求后直接 return(无值),这会让调用方的 .then() 永远不执行,Promise 悬停,后续所有依赖它的逻辑(比如 uni.hideLoading()、按钮状态重置)全部停滞。
正确做法是返回一个已决议的 Promise:
if (pendingSet.has(key)) {
console.log('重复请求,已拦截');
return Promise.resolve({ data: null, errMsg: 'repeat_request' });
}
同时注意:pendingSet.delete(key) 必须放在 complete 回调里,不能只放 success,否则失败请求不会释放锁。
小程序里还有哪些隐藏的点击穿透问题?
即使加了状态锁,仍可能因底层机制导致二次触发:
- 父容器缺少
catchtouchmove,导致 touch 事件穿透到底层 view,又被当成新点击 - iOS 小程序对双击识别不稳定,
@tap可能触发两次,需加event.detail.tapCount === 1过滤 -
uni.showToast关闭动画未结束时用户就点按钮,isSubmitting已重置,但实际请求还在 pending —— 应在uni.hideToast的success回调里才允许重置状态
多层防护不是过度设计,而是小程序平台本身事件模型不统一的必然结果。状态锁 + 请求拦截 + UI 遮罩 + tapCount 校验,缺一不可。










