为了阻止用户在某些情况下,短时间内重复点击某个按钮,导致前端向后端重复发送多次请求。
通过控制 loading 来设置 loading,或者 disabled 也行,从而来控制按钮的是否可以点击。通过在 handleSubmit 函数未获取到服务器接口响应之前,该按钮一直处于不可用的状态,直到接收到服务器接口相应后,我们再将按钮恢复为可用状态。
登录
handleSubmit () {
this.loading = true
setTimeout(() => {
this.loading = false
}, 1000)
}
// 或者
handleSubmit () {
if (timer) {
clearTimeout(timer)
}
timer = setTimeout(() => {
this.submit()
}, 300)
}
export const operationConfirm = function (attr, message) {
const that = this
return new Promise((resolve, reject) => {
that.$antdConfirm({
title: '提示',
content: message || `确定要执行${attr}操作吗?`,
onOk () {
resolve(true)
},
onCancel () {
resolve(false)
},
})
})
}
// loading 处理重复提交(注意 Vue 实例上添加 submitLoading 字段)
export const handleRepeatSubmit = async function (message, fn, cb, loading = 'submitLoading') {
if (!fn) return
const confirm = message && await operationConfirm.call(this, message)
const that = this
if (!message || confirm) {
if (that[loading]) return
that[loading] = true
try {
const data = await fn()
cb && cb(data)
that[loading] = false
} catch (error) {
that[loading] = false
}
}
}
// 使用如下, fn 为接口调用
async handleConfirm () {
const { validate, form } = this.$refs.cancel
if (!await validate()) {
return
}
handleRepeatSubmit.call(this, null, () => fn(), () => {
this.visible = false
this.$antdMessage.success('操作成功')
this.$emit('refresh')
})
},
使用 axios 第三方库,request 拦截器来拦截重复请求。
更多详情,请查看:axios 全局阻止重复请求。
为避免用户在短时间内点击过快,我们给点击设置点击间隙,即做防抖处理。其实就是比较当前点击和上一次点击的时间差,如果时间差小于设置的值,即阻止点击事件,同时记录这次点击事件,以便和下次点击做比较。了解防抖基础知识,请查看:防抖和节流的区别以及实现。
// 防抖
export const antiShake = (fn, t) => {
let delay = t || 500
let timer
return function () {
let args = arguments;
if (timer) clearTimeout(timer)
let callNow = !timer
timer = setTimeout(() => {
timer = null
}, delay)
if (callNow) fn.apply(this, args)
}
}
如何使用呢?
//引入防抖文件
import { antiShake } from '@/utils/utils.js';
// 给按钮添加防抖
startDrawPolygon:antiShake(function(){
......
})
进阶一:
除了上述实现方案,我们也可以自定义指令来实现点击事件防抖功能。
Vue.directive('antiShake', {
// 被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。
/**
* el 指令所绑定的元素,可以用来直接操作 DOM 。
* binding 一个对象,包含绑定的值
*/
inserted: function(el, binding) {
const { callback, time } = binding.value
el.callback = callback
el.time = time
el.timeCall = null
el.addEventListener('click', () => {
clearTimeout(el.timeCall)
el.timeCall = setTimeout(() => {
el.callback()
}, el.time || 500)
})
},
......
})
通过定时器 setTimeout 延时执行 click 回调,当 el.time || 500 时间内,再次触发 clearTimeout 清除定时器;
Vue.directive('antiShake', {
......
// 所在组件的 VNode 更新时调用
update: function(el, binding) {
console.log('update')
const { callback, time } = binding.value
el.callback = callback
el.time = time
},
})
更新挂载到 el 上的 callback 和 time ,当 v-antiShake 绑定的值更新后,事件触发跟新后的 callback。
如何使用呢?
handleClick: {
time: 1000,
callback: () => {
console.log(1111)
}
}
指令的值 handleClick 未作深度监听(watch 之 deep),只有对象整体改变才会触发指令中的 update 钩子函数。
进阶二:
当然我们全局配置防抖,即把 click 添加的防抖处理事件,添加到 Vue 实例上。
const on = Vue.prototype.$on
// 防抖处理
Vue.prototype.$on = function (event, func) {
let timer
let newFunc = func
if (event === 'click') {
newFunc = function () {
clearTimeout(timer)
timer = setTimeout(function () {
func.apply(this, arguments)
}, 500)
}
}
on.call(this, event, newFunc)
}
参考案例:
vue全局配置防抖和节流
防抖与节流(vue-自定义指令)
vue 自定义防抖/节流指令的实现
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
更新
使用 lodash 库的 debounce 来配置防抖功能。
待完善.......