【实战】Vue自定义防抖指令

11,100 阅读4分钟

前言

最近项目中经常出现由于用户多次点击导致的问题,防抖/节流使用的很多,因此,想通过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.2s,0.4s,0.6s,0.8s进行连续点击,触发searchAPI。

【防抖后的效果】: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,不会出现覆盖的情况。

当然是使用addEventListenerIE浏览器要用  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:前端小白一枚,欢迎留言报错,感谢~