vue3实现防抖节流指令

4,788 阅读3分钟

首先我们来看一下,vue3的指令的写法与vue2有什么不同呢?

「Vue2.0」directive.js

// 注册全局自定义指令
Vue.directive('xxx', {
    // 指令第一次绑定到元素时「初始化」……
    bind: function (el, binding, vnode, oldVnode) {
        // el:绑定指令的DOM元素对象
        // binding:数据对象
        //   + name:不带“v-”的指令名字
        //   + rawName:带“v-”的指令名字「含值和修饰符」
        //   + value:指令绑定的值   v-xxx="1+1" -> value:2
        //   + expression:指令表达式  v-xxx="1+1" -> expression:"1+1"
        //   + arg:传给指令的参数   v-xxx:n -> arg:"n"
        //   + modifiers:修饰符对象  v-xxx.stop.finish -> modifiers:{stop:true,finish:true}
        // vnode:Vue编译生成的虚拟节点
        // oldVnode:上一个虚拟节点
    },
    // 被绑定元素插入父节点时「父节点不一定非要插入到文档中,一般是插入到DOM中」……
    inserted: function () {},
    // 所在组件的VNode更新时「指令的值可能发生了改变,也可能没有」……
    update: function (el, binding, vnode, oldVnode) {
        // binding:数据对象
        //   + oldArg
        //   + oldValue
        //   + ...
    },
    // 所在组件的VNode及其子VNode全部更新后……
    componentUpdated: function () {},
    // 指令与元素解绑时……
    unbind: function () {}
});


「Vue3.0」directive.js

export default function directive(app) {
    app.directive('xxx', {
        // 指令首次绑定到元素且在安装父组件之前...「等同于bind」
        beforeMount(el, binding, vnode, prevVnode) {
            // binding:数据对象
            //   + arg:传给指令的参数   v-xxx:n -> arg:"n"
            //   + modifiers:修饰符对象 v-xxx.stop -> modifiers:{stop:true}
            //   + value:指令绑定的值   v-xxx="1+1" -> value:2
            //   + oldValue:之前绑定的值
        },
        // 安装绑定元素的父组件时...「等同于inserted」
        mounted() {},
        // 在包含组件的VNode更新之前...
        beforeUpdate() {},
        // 在包含组件的VNode及其子VNode更新后...「等同于componentUpdated」
        updated() {},
        // 在卸载绑定元素的父组件之前...
        beforeUnmount() {},
        // 指令与元素解除绑定且父组件已卸载时...「等同于unbind」
        unmounted() {}
    });
};

// main.js
import {
    createApp
} from 'vue';
import App from './App.vue';
import directive from './directive';
const app = createApp(App);
directive(app);
app.mount('#app');

可以看出,vue3指令的钩子和vue2有所区别,其他在写法上与vue2没有太大的区别。

防抖和节流相信大家也很熟悉啦,在这里就不多介绍了。我是这么区别防抖和节流的,多次触发事件只执行一次是防抖,按规定的频率执行是节流。

接下来,下面让我们一起来实现v-debouncev-throttle 吧。

//先定义throttle和debounce方法
//从loadsh里复制出来的
const throttle = function throttle(func, wait) {
    if (typeof func !== "function") throw new TypeError('func must be a function!');
    wait = +wait;
    if (isNaN(wait)) wait = 300;
    let timer = null,
        previous = 0,
        result;
    return function proxy(...params) {
        let now = +new Date,
            remaining = wait - (now - previous),
            self = this;
        if (remaining <= 0) {
            if (timer) {
                clearTimeout(timer);
                timer = null;
            }
            previous = now;
            result = func.apply(self, params);
            return result;
        }
        if (!timer) {
            timer = setTimeout(() => {
                clearTimeout(timer);
                timer = null;
                previous = +new Date;
                result = func.apply(self, params);
            }, remaining);
        }
        return result;
    };
};

const debounce = function debounce(func, wait, immediate) {
    if (typeof func !== "function") throw new TypeError('func must be a function!');
    if (typeof wait === "undefined") {
        wait = 500;
        immediate = false;
    }
    if (typeof wait === "boolean") {
        immediate = wait;
        wait = 500;
    }
    if (typeof immediate === "undefined") {
        immediate = false;
    }
    if (typeof wait !== "number") throw new TypeError('wait must be a number!');
    if (typeof immediate !== "boolean") throw new TypeError('immediate must be a boolean!');
    let timer = null,
        result;
    return function proxy(...params) {
        let self = this,
            callNow = !timer && immediate;
        if (timer) clearTimeout(timer);
        timer = setTimeout(() => {
            clearTimeout(timer);
            timer = null;
            if (!immediate) result = func.apply(self, params);
        }, wait);
        if (callNow) result = func.apply(self, params);
        return result;
    };
}

const common_config = {
    beforeMount(el, binding) {
        let {
            func,
            wait = 300,
            immediate = true,
            params = [],
            type = 'click'
        } = binding.value;
        const handle = binding.name === 'debounce' ? debounce : throttle,
            proxy = function proxy(...args) {
                return func.call(this, ...params.concat(args));
            };
        el.$type = type;
        el.$handle = handle(proxy, wait, immediate);
        el.addEventListener(el.$type, el.$handle);
    },
    unmounted(el) {
        el.removeEventListener(el.$type, el.$handle);
    }
};
Vue.directive('debounce', common_config);
Vue.directive('throttle', common_config);

//使用
<button
  class="submit"
  v-throttle="{
    func: submitHandle,
    wait: 1000,
    immediate: true,
    params: [10, 20],
    type: 'click',
  }"
/>