前言
最近项目中经常出现由于用户多次点击导致的问题,防抖/节流使用的很多,因此,想通过vue自定义指令的方式来实现。后面有需要使用防抖的地方只要使用改指令即可。
原代码
<button @click="sayHello">提交</button>
sayHello() {
console.log('Hello!')
}
我想达到的效果:
<button v-throttle=“200” @click="sayHello">提交</button>
通过此设置,可以让提交按钮在200ms内的多次点击只能执行一次,并且刚点击时就执行。若不设置时间(200),则默认2000ms内只执行一次。
防抖 / 节流的区别和选择
(0)节流/防抖形象的解释
<1>节流是指一定时间内js方法只跑一次。比如人的眨眼睛,就是一定时间内眨一次。这是函数。
<2>防抖是指频繁触发的情况下,只有足够的空闲时间,才执行代码一次。比如生活中的坐公交,就是一定时间内,如果有人陆续刷卡上车,司机就不会开车。只有别人没刷卡了,司机才开车。
(1)区别
在我看来它们的差别在于应用场景,举例如下:
【防抖后的效果】:0.8s后才真正进行searchAPI的发送;
【节流的效果】:0.2s时发现有人点击,立即触发searchAPI接口,并且在x秒内,用户点击无效。
从网上盗了个动图(图片来源于VUE防抖与节流的最佳解决方案——函数式组件),感觉看起来比较清晰:
(2)选择
我这边需要在用户刚点击的时候就立即响应,后面几秒钟的点击无效,是节流的效果,所以选择节流。
如何创建自定义指令
从Vue官网,自定义指令教程(戳这里可以看详细信息),找到下面这两点可供我使用:
(1)自定义指令的钩子函数:bind,inserted,update,componentUpdated,unbind
我选用bind,只需要一次性的初始化就够了。
(2)钩子函数参数
el:可直接操作DOM(例如, el.addEventListener
, el.onclick
)。
binding:可通过value获得指令绑定值。
思考:如何在不妨碍原本click事件的情况下,添加监听click事件
onclick事件的处理程序在有多个的情况下,后者会覆盖前者。addEventListener给一个事件注册多个listener,不会出现覆盖的情况。
当然是使用addEventListener
(IE浏览器要用 attachEvent
,然而我的项目中只需要支持chrome即可,就不考虑啦)
第一种方案
实现思路
如何控制 sayHello()
方法是否执行呢?我首先想到的是通过一个变量 isDisableClick
控制,通过注册的click事件,把变量 isDisableClick
存于dom中, sayHello()
方法执行的时候先判断变量 isDisableClick
。
定义防抖指令:
Vue.directive('throttle', {
bind: function (el, binding) {
let throttleTime = binding.value // 节流时间
if (!throttleTime) { // 用户若不设置节流时间,则默认2s
throttleTime = 2000
}
let cbFun
el.addEventListener('click', () => {
el.isDisableClick = true
if (!cbFun) {
cbFun = setTimeout(() => {
el.isDisableClick = false
cbFun = null
}, throttleTime)
}
}, true)
}
})
使用指令:
<button @click="sayHello" ref="target" v-throttle>提交</button>
sayHello() {
if (!this.$refs.target.isDisableClick) {
console.log('Hello!')
}
}
问题:
使用时还要写多余的代码,感觉用起来不愉快,丢弃!
第二种方案(网上看到的)
实现思路
通过 disabled
属性阻止onclick事件的触发
定义防抖指令:
Vue.directive('throttle', {
bind: function (el, binding) {
let throttleTime = binding.value // 节流时间
if (!throttleTime) { // 用户若不设置节流时间,则默认2s
throttleTime = 2000
}
let cbFun
el.addEventListener('click', () => {
if (!el.disabled) {
el.disabled = true
cbFun = setTimeout(() => {
el.disabled = false
cbFun = null
}, throttleTime)
}
}, true)
}
})
使用指令:
<button @click="sayHello" v-throttle>提交</button>
sayHello() {
console.log('Hello!')
}
问题1:
对于button可以使用disabled
属性让其不触发,但有些按钮是用 div / span 等这种实现的,disabled
属性并不能阻止div的onclick事件。
问题1的解决方法:
设置样式 pointer-events: none
可阻止 div / span 的onclick事件。
问题2:
pointer-events:none
的作用是让元素实体“虚化”。例如一个应用pointer-events:none
的按钮元素,则我们在页面上看到的这个按钮,只是一个虚幻的影子而已,您可以理解为海市蜃楼,幽灵的躯体。当我们用手触碰它的时候可以轻易地没有任何感觉地从中穿过去。
摘自张鑫旭的CSS3 pointer-events:none应用举例及扩展
由于设置为pointer-events:none
后,相当于该元素已经不存在了,那么这种情况的点击会不会穿透导致点击到它的外层呢?
结果是肯定的,设置pointer-events:none
后,点击了子节点是无效的,但同时相当于点击了其父节点。
第三种方案
定义防抖指令:
Vue.directive('throttle', {
bind: (el, binding) => {
let throttleTime = binding.value; // 防抖时间
if (!throttleTime) { // 用户若不设置防抖时间,则默认2s
throttleTime = 2000;
}
let cbFun;
el.addEventListener('click', event => {
if (!cbFun) { // 第一次执行
cbFun = setTimeout(() => {
cbFun = null;
}, throttleTime);
} else {
event && event.stopImmediatePropagation();
}
}, true);
},
});
使用指令:
<button @click="sayHello" v-throttle>提交</button>
sayHello() {
console.log('Hello!')
}
PS:前端小白一枚,欢迎留言报错,感谢~