vue项目中写了好多防抖函数以后, 想利用自定义指令实现一下。
开始想法是实现一个自定义的click事件:
// main.js
import Directives from '@/directives'
Vue.use(Directives)
// directives/index.js
import debounceClick from './debounceClick'
const directives = {
debounceClick,
}
export default {
install(Vue) {
Object.keys(directives).forEach(key => {
Vue.directive(key, directives[key])
})
},
}
// directives/debounceClick
import debounce from 'lodash/debounce'
export default {
// 指令第一次绑定元素时使用,在这里进行初始化
bind: (el, { arg, value }) => {
if (arg && !/^\d+$/.test(arg)) {
throw new Error('时间填写不对')
}
const time = Number(arg) || 300
const debounceClickFn = debounce(value, time, {
leading: true,
trailing: false,
})
el._debounceClick = debounceClickFn
el.addEventListener('click', el._debounceClick, true)
},
unbind: el => {
el.removeEventListener('click', el._debounceClick)
delete el._debounceClick
},
}
使用方法就像下面这样:
<div v-debounce-click="onClickName">点我呀</div>
<div v-debounce-click:5000="onClickName">这是一个5秒的防抖</div>
本来像上面那样就解决了需求。 但是, 本着学习的态度, 网上搜一些关于自定义指令防抖的文章, 发现了不一样的思路。
我的思路就是完全自定义一个自己的防抖指令函数, 来替代原有的@click事件, 而找了几篇文章之后发现还有别的思路:@click正常写, 另外添加一些自定义指令做控制, 类似下面这样:
<div @click="onClickName" v-debounce>点我呀</div>
而实现原理是利用stopImmediatePropagation来屏蔽/放开元素加载本身绑定的click事件。
以下摘自【实战】Vue自定义防抖指令 - 掘金 (juejin.cn), 博主写的是throttle:
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>
不过我每次自己写 节流/防抖 都写不好, 生怕漏了什么边界条件, 所以稍稍改了一下:
// directives/debounce
import debounce from 'lodash/debounce'
export default {
// 指令第一次绑定元素时使用,在这里进行初始化
bind: (el, { arg, value = {} }) => {
if (!arg) {
throw new Error('请填写事件名称')
}
if (value.time && !/^\d+$/.test(value.time)) {
throw new Error('时间填写不对')
}
const time = Number(value.time) || 300
let flag = true // 是否调用触发的事件
const debounceClickFn = debounce(() => (flag = true), time, {
leading: true,
trailing: false,
...value,
})
el._arg = event => {
flag = false
debounceClickFn()
if (!flag) {
event?.stopImmediatePropagation()
}
}
el.addEventListener(arg, el._arg, true)
},
unbind: (el, { arg }) => {
el.removeEventListener(arg, el._arg)
delete el._arg
},
}
使用方式如下:
<div @click="onClickName" v-debounce:click>点我呀</div>
<div @click="onClickName" v-debounce:click="{time: 5000}">这是一个5秒的防抖</div>
由于思路打开以后, 我觉得可以把这个指令给扩充一下了。两个自定义指令, 一个v-throttle一个v-debounce。 传递给指令的参数arg字段, 也就是:后面的参数, 可以携带click或者别的一些事件参数, 就更加通用。 另外自定义指令的修饰符对象modifiers, 可以拿来用作一些设置的集成, 这个根据项目需求可以自己设置。