仅作为记录,方便未来的自己(记录思路)
本来在写着vue3,想通过添加一个自定义指令在不修改原有@click的情况下去实现防抖/节流,本想着让ai来写,结果ChatGPT和DeepSeek写的都有问题,大致思路就是通过拦截替换原有的事件,俩ai都想通过el._vei.onClick来获取原有事件,奈何我前段时间更新了vue3版本,结果这个事件换位置了,导致我测试时一直失败,查找资料发现这种在3.3.4以及以下是有效的
//3.3.4以及以下是有效
const fn = el._vei.onClick;
3.3.5及以后发生了变化,需要通过以下代码获取
// 3.3.5及以后有效
const vei_key = Object.getOwnPropertySymbols(el)?.[0];
const fn = el[vei_key].onClick;
只能自己上手了,我换了一个思路,为什么要去拦截原有事件,把原有事件的执行时机交给防抖/节流中去执行(还需要去操心this指向问题),我只需要在不该执行的时候去阻止这次执行即可,由此思路写出了下面的代码(代码可能不够完善,只提供个思路)
debounce-click.js文件:
// debounce-click.js
const name = 'debounce-click';
const directive = {
mounted(el, binding) {
const delay = binding.value ?? 300;
let timer = null;
const debouncedHandler = (event) => {
// 如果是模拟出来的合成事件则不去执行stopImmediatePropagation,让流程正常走完
if (event.isSynthetic) return;
// 拦截用户手动触发的原始点击事件
event.stopImmediatePropagation();
// 每次点击都重置定时器
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
timer = null;
// 派发一个合成的事件
const newEvent = new event.constructor(event.type, event);
newEvent.isSynthetic = true;
el.dispatchEvent(newEvent);
}, delay);
};
el.addEventListener('click', debouncedHandler, true);
el._debouncedHandler = debouncedHandler;
},
beforeUnmount(el) {
if (el._debouncedHandler) {
el.removeEventListener('click', el._debouncedHandler, true);
delete el._debouncedHandler;
}
}
};
export default {
name,
directive
};
throttle-click.js文件:
// throttle-click.js
const name = 'throttle-click';
const directive = {
mounted(el, binding) {
const delay = binding.value ?? 300;
let isDisabled = false;
const throttledHandler = (event) => {
if (isDisabled) return event.stopImmediatePropagation();
isDisabled = true;
setTimeout(() => isDisabled = false, delay);
};
el._throttledHandler = throttledHandler;
el.addEventListener('click', throttledHandler, true);
},
beforeUnmount(el) {
if (el._throttledHandler) {
el.removeEventListener('click', el._throttledHandler);
delete el._throttledHandler;
}
}
};
export default {
name,
directive
};
注册指令(vue3)
这里我是方便在vue中去注册自定义指令所以把代码写成了这种结构,如果是其他前端框架直接手动调用directive的mounted和beforeUnmount也是可以正常运行达到预期效果的(传递个dom元素和传递和时间即可)
// index.js 提供一个统一注册的方法出去
import debounceClick from './debounce-click.js';
import throttleClick from './throttle-click.js';
export default function registerDirectives(app) {
const directives = [debounceClick, throttleClick];
directives.forEach(directive => {
app.directive(directive.name, directive.directive);
});
}
// main.js
import registerDirectives from '@/directives';
const app = createApp(App);
// 批量注册自定义指令
registerDirectives(app);
最后就可以这样子使用了
// vue
<div v-debounce-click @click="onClick" />
<div v-throttle-click @click="onClick" />