VUI創建日誌(二)——防抖節流組件的實現

1. 項目實現介紹

vue 項目搭建參考《Webpack4 搭建 Vue 項目》

文檔使用 vuepress, 官方文檔 https://vuepress.vuejs.org

發佈文檔 github pages + gh-page

項目地址 https://github.com/zxpsuper/vui-vue<img src="https://img.shields.io/github...;>

文檔地址 https://zxpsuper.github.io/vui-vue

組件地址 https://zxpsuper.github.io/vui-vue/components/throttle.html

處於自我摸索階段,期待留下您的寶貴意見!

2. Throttle 組件的實現

  1. 首先,寫一個防抖節流的通用函數
/**
 * @param {function} func 執行函數
 * @param {number} time 防抖節流時間
 * @param {boolean} isDebounce 是否爲防抖組件
 * @param {this} ctx this 的指向
*/
const debounce = (func, time, isDebounce, ctx) => {
    var timer, lastCall, rtn;
    if (isDebounce) {
        rtn = (...params) => {
            if (timer) clearTimeout(timer);
            timer = setTimeout(() => {
                func.apply(ctx, params);
            }, time);
        };
    } else {
        rtn = (...params) => {
            const now = new Date().getTime();
            if (now - lastCall < time && lastCall) return;
            lastCall = now;
            func.apply(ctx, params);
        };
    }
    return rtn;
};
  1. 使用抽象組件
export default {
    name: 'Throttle',
    abstract: true,
    props: {
        time: {
            type: Number,
            default: 800,
        },
        events: {
            type: String,
            default: 'click',
        },
        isDebounce: {
            type: Boolean,
            default: false,
        },
    },
    created() {
        this.eventKeys = this.events.split(','); // 分隔事件
        this.originMap = {}; // 儲存事件,用於重新render時與子事件的對比
        this.debouncedMap = {}; // 儲存防抖節流事件
    },
    render() {
        const vnode = this.$slots.default[0];
        this.eventKeys.forEach(key => {
            const target = vnode.data.on[key];
            if (target === this.originMap[key] && this.debouncedMap[key]) {
                vnode.data.on[key] = this.debouncedMap[key];
            } else if (target) {
                this.originMap[key] = target;
                this.debouncedMap[key] = debounce(
                    target,
                    this.time,
                    this.isDebounce,
                    vnode
                );
                vnode.data.on[key] = this.debouncedMap[key]; // 重寫子組件的事件
            }
        });
        return vnode;
    },
};

3. 使用組件

需全局或者組件內註冊一下,這裏只展示全局註冊代碼:

Vue.component("Throttle", Throttle);

使用方法:

<Throttle :time="5000"  isDebounce>
    <span @click="decounceFunction">
        <Button color="purple" light>防抖按鈕</Button>
    </span>
</Throttle>

4. 存在問題與解決方法

當頁面元素在防抖節流時間內發生了更新(渲染)(可以用定時器修改頁面,如頁面倒計時),那麼此組件會重新執行一遍

this.debouncedMap[key] = debounce(target,this.time,this.isDebounce,vnode);

導致防抖節流失效,目前的解決方法是在此組件的子元素添加 v-once,如下:

<Throttle :time="5000"  isDebounce>
    <span @click="decounceFunction" v-once>
        <Button color="purple" light>防抖按鈕</Button>
    </span>
</Throttle>

可解決此問題,有更好的方法請在評論區留言

5. 完整代碼

/*
 * @descript: 防抖節流組件,前提是頁面在等待時間內無其他渲染方可使用,重新渲染導致 debounce() 函數不斷重置
 * @Author: super
 * @Date: 2019-04-09 14:21:18
 * @Last Modified by: super
 * @Last Modified time: 2019-10-24 16:37:43
 */

const debounce = (func, time, isDebounce, ctx) => {
    var timer, lastCall, rtn;
    if (isDebounce) {
        rtn = (...params) => {
            if (timer) clearTimeout(timer);
            timer = setTimeout(() => {
                func.apply(ctx, params);
            }, time);
        };
    } else {
        rtn = (...params) => {
            const now = new Date().getTime();
            if (now - lastCall < time && lastCall) return;
            lastCall = now;
            func.apply(ctx, params);
        };
    }
    return rtn;
};

export default {
    name: 'Throttle',
    abstract: true,
    props: {
        time: {
            type: Number,
            default: 800,
        },
        events: {
            type: String,
            default: 'click',
        },
        isDebounce: {
            type: Boolean,
            default: false,
        },
    },
    created() {
        this.eventKeys = this.events.split(',');
        this.originMap = {};
        this.debouncedMap = {};
    },
    render() {
        const vnode = this.$slots.default[0];
        this.eventKeys.forEach(key => {
            const target = vnode.data.on[key];
            if (target === this.originMap[key] && this.debouncedMap[key]) {
                vnode.data.on[key] = this.debouncedMap[key];
            } else if (target) {
                this.originMap[key] = target;
                this.debouncedMap[key] = debounce(
                    target,
                    this.time,
                    this.isDebounce,
                    vnode
                );
                vnode.data.on[key] = this.debouncedMap[key];
            }
        });
        return vnode;
    },
};

總結

本文是對render及抽象組件的使用總結,若有錯誤,望指出共同進步。

更多推薦

前端進階小書(advanced_front_end)

前端每日一題(daily-question)

webpack4 搭建 Vue 應用(createVue)

Canvas 進階(一)二維碼的生成與掃碼識別

Canvas 進階(二)寫一個生成帶logo的二維碼npm插件

Canvas 進階(三)ts + canvas 重寫”辨色“小遊戲

Canvas 進階(四)實現一個“刮刮樂”遊戲

VUI創建日誌(一)——圖片懶加載指令的實現


<h1>最後祝大家1024節日快樂哈哈😍</h1>

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章