引言
在日常前端开发中,我们经常会面对一个让人头疼的问题:按钮被用户点击了两次以上,导致出现重复提交表单或者发送重复的请求。这个问题常见而且恼人。为了解决这个问题,我们需要一个又简单又实用的方法,可以在不搞乱原有代码的情况下,有效地防止按钮被连续点击。
背景
随着网页应用变得越来越复杂,用户在页面上的交互也变得越来越频繁。这就使得按钮被不小心点击多次的情况变得非常普遍。一般的解决方法存在一些问题,比如改动原有代码太多,不够灵活等。因此,我们需要一种更好的、通用的按钮防连点方法。
挑战
在解决按钮被连点的问题时,我们要面临一些挑战。首先,解决方法得适应各种情况,比如表单提交、异步请求等。其次,我们需要确保解决方法不会让我们的原有代码变得混乱,同时还要具备足够的灵活性。最后,我们希望不修改原有代码的情况下提供按钮防连点。
目标
本文的目标是为大家提供一个简单易用的按钮防连点解决方案。我们会深入讲解方案的设计原理和实现细节,并且会附上完整的源码解析。通过学习本文,你将能够理解这个解决方案的原理,同时学会如何在实际项目中应用这个方法。希望通过这篇文章,按钮防连点问题不再让你感到头疼,反而变得得心应手。
防连点原理概述
在开始实现我们的按钮防连点终极解决方案之前,让我们首先理解一下连点问题的本质以及为什么传统的解决方案可能存在一些问题。
连点问题的本质
按钮连点问题的核心在于,用户在短时间内多次点击按钮,导致触发相同的操作。这可能引发一系列不良后果,比如重复提交表单、重复发送请求等。为了解决这个问题,我们需要一种机制来在用户点击按钮后一段时间内禁用按钮,防止其再次触发相同的操作。
常规解决方案的局限性
传统的解决方案往往通过在点击按钮后添加禁用状态,然后在一段时间后再启用按钮,来防止连点问题。然而,这种方法存在一些局限性。首先,它可能需要修改原有的按钮组件,使得在多个地方应用时不够灵活。其次,由于采用了定时器等机制,可能导致在某些情况下并不准确,或者在异步操作中存在问题。
给按钮加指令
创建一个可复用的Vue自定义指令,该指令能够动态地管理按钮的状态,以防止用户在短时间内多次点击按钮。关键之处在于,我们还将支持外部传递参数,以自定义按钮的禁用时间。
创建可复用的防连点按钮组件
首先,我们需要创建一个按钮组件,该组件可以接受我们的自定义指令。这样,我们就可以在需要的按钮上应用这个指令。
xml
<template>
<button v-prevent-duplicate-clicks="2000" @click="handleClick">防连点按钮</button>
</template>
<script>
import preventDuplicateClicks from '@/path-to-your-file/preventDuplicateClicks';
export default {
directives: {
preventDuplicateClicks,
},
methods: {
handleClick() {
// 处理按钮点击事件的业务逻辑
},
},
};
</script>
在上述代码中,我们创建了一个按钮组件,通过 v-prevent-duplicate-clicks
指令来防止按钮的连点行为。并且,我们通过传递参数 "2000"
指定了按钮禁用的时间为2秒。
指令文件
现在,创建一个名为 preventDuplicateClicks.js
的文件,该文件包含我们的自定义指令。
ini
// preventDuplicateClicks.js
const preventDuplicateClicks = {
mounted(el, binding) {
const { value } = binding;
el.addEventListener('click', () => {
if (!el.disabled) {
el.disabled = true;
setTimeout(() => {
el.disabled = false;
}, value || 1000); // 默认1秒后恢复按钮点击
}
});
},
};
export default preventDuplicateClicks;
在上述代码中,我们定义了一个名为 preventDuplicateClicks
的自定义指令,它在按钮被点击时阻止多次点击。通过 setTimeout
来实现按钮在一定时间后恢复点击。此外,我们在指令上支持了外部参数的传递,用于自定义按钮的禁用时间。
以为估计是80%的前端的解决方案,我在面试中也问过类似的问题,很多人能给出指令的写法已经很好了。有如下问题:
- 侵入已有代码:如果是老的项目,都要添加这个功能,要去批量改,很麻烦;
- 禁用时间错误:我们假设给的参数是2秒,一个接口请求超过2秒, 用户在请求响应之前依然会重复发起请求。如果接口几十毫秒就返回(是异常,让重试),用户也得等2秒;
那么有没有更好的解决方案了,肯定是有的,我们来分析以上两个问题:
- 侵入代码:我们通过重写已有组件可以做到,假设用的el-button,我们在全局把el-button覆盖成我们自己的即可
- 禁用时间错误:既然设置是错的,那我们就不设置,按钮肯定都有一个onClick的函数,我们只要在onClick执行之前设置禁用,执行后启用即可。
终极解决方案
有了以上的分析,我们直接贴代码吧,我司用的vue + ant design vue,其他组件库,类似写法吧。
vue
<script>
import { Button as AntButton } from 'ant-design-vue'
export default {
name: 'AButton',
props: {
...AntButton.props,
delay: {
type: Number,
default: 300
}
},
data() {
return {
customLoading: false,
ownDisabled: false
}
},
computed: {
allProps() {
return Object.assign({}, this.$props, {
loading: this.customLoading || this.loading,
type: this.$props.type || 'primary'
})
}
},
methods: {
async handler (...arg) {
if (this.ownDisabled) return
this.customLoading = true
this.ownDisabled = true
const { click: preClick } = this.$listeners || {}
const ret = preClick(...arg)
try {
await Promise.resolve(ret)
} finally {
this.customLoading = false
let timer = setTimeout(() => {
this.ownDisabled = false
clearTimeout(timer)
timer = null
}, this.delay)
}
}
},
render() {
return (
<AntButton props={this.allProps} onClick={this.handler}>
{this.$slots?.default}
</AntButton>
)
}
}
</script>
需要注意的是,使用的时候 onClick
需要返回promise,这算一个很容易遵守的约定吧。另外加个默认延迟300ms,目的我记得好像是为了避免路由跳转时的问题,大家可以自己调整。