Vue實現函數防抖組件

最近在掘金看到兩篇很是不錯的文章:vue

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

在這裏我就不介紹函數防抖/函數節流的概念了,將這樣的功能封裝是組件真的是很是實用。bash

經過HOC(高階組件)的方式進行封裝的思路我也很喜歡,這裏也想分享一個相似的封裝方法markdown

抽象組件

這裏我使用了abstract: true來建立一個抽象組件。app

咱們經常使用的transitionkeep-alive就是一個抽象組件。抽象組件是無狀態的,一樣也是「不存在的」,它本身並不會被渲染爲實際的DOM,而是直接返回以及操做它的子元素。函數

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

<Debounce>
    <button>123</button>
</Debounce>
複製代碼

會被渲染成:this

<button>123</button>
複製代碼

實現

這裏直接貼出組件代碼:spa

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渲染)。

相關文章
相關標籤/搜索