Vue實現函式防抖元件

笨笨小撒發表於2019-03-03

最近在掘金看到兩篇非常不錯的文章:

這兩篇文章中作者都分享了關於把函式防抖/函式節流包裝成通用元件的經驗。

在這裡我就不介紹函式防抖/函式節流的概念了,將這樣的功能封裝是元件真的是非常實用。

通過HOC(高階元件)的方式進行封裝的思路我也很喜歡,這裡也想分享一個類似的封裝方法

抽象元件

這裡我使用了abstract: true來建立一個抽象元件。

我們常用的transitionkeep-alive就是一個抽象元件。抽象元件是無狀態的,同樣也是“不存在的”,它自己並不會被渲染為實際的DOM,而是直接返回以及操作它的子元素。

例如對於模板(Debounce是一個抽象元件):

<Debounce>
    <button>123</button>
</Debounce>
複製程式碼

會被渲染成:

<button>123</button>
複製程式碼

實現

這裡直接貼出元件程式碼:

const debounce = (func, time, ctx) => {
    let timer
    const rtn = (...params) => {
        clearTimeout(timer)
        timer = setTimeout(() => {
            func.apply(ctx, params)
        }, time)
    }
    return rtn
}

Vue.component(`Debounce`, {
    abstract: true,
    props: [`time`, `events`],
    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, vnode)
                vnode.data.on[key] = this.debouncedMap[key]
            }
        })
        
        return vnode
    },
})
複製程式碼

Debounce元件會接受timeevents(用逗號分隔)的兩個引數。

render函式中,Debounce元件修改了子VNode的事件,再將其返回回去。

使用

然後我們來使用一下:

<div id="app">
    <Debounce :time="1000" events="click">
        <button @click="onClick($event, 1)">click+1 {{val}}</button>
    </Debounce>
    <Debounce :time="1000" events="click">
        <button @click="onClick($event, 2)">click+2 {{val}}</button>
    </Debounce>
    <Debounce :time="1000" events="mouseup">
        <button @mouseup="onAdd">click+3 {{val}}</button>
    </Debounce>
    <Debounce :time="1000" events="click">
        <button @mouseup="onAdd">click+3 {{val}}</button>
    </Debounce>
</div>
複製程式碼
const app = new Vue({
    el: `#app`,
    data () {
        return {
            val: 0,
        }
    },
    methods: {
        onClick ($ev, val) {
            this.val += val
        },
        onAdd () {
            this.val += 3
        }
    }
})
複製程式碼

使用指令

使用自定義指令也是一種思路,不過指令的bind發生在created的回撥中,也就是晚於事件的初始化的,這樣的話就不能通過修改vnode.data.on來改變繫結的事件回撥,只能自己來繫結事件了:

Vue.directive(`debounce`, {
    bind (el, { value }, vnode) {
        const [target, time] = value
        const debounced = debounce(target, time, vnode)
        el.addEventListener(`click`, debounced)
        el._debounced = debounced
    },
    destroy (el) {
        el.removeEventListener(`click`, el._debounced)
    }
})
複製程式碼

這裡要注意的一點是,指令binding.value的求值過程和事件繫結是不同的,並不支援onClick($event, 2)的寫法,因此如果這樣的繫結就只能再包一層了:

<button v-debounce="[($ev) => { onClick($ev, 4) }, 500]">click+4 {{val}}</button>
複製程式碼

小結

使用抽象元件的好處是提高了元件的通用性,不會因為元件的使用而汙染DOM(新增並不想要的div標籤等)、可以包裹任意的單一子元素,當然也有缺點,比如使用時要注意子元素只能包含一個根,使用起來也比較囉嗦(參考文章中ButtonHoc在使用時更簡潔一些,但相應的是隻能作為Button渲染)。

相關文章