Read two very good articles on the Nuggets recently:

  • Implement Throttle or debounce as a Vue component or plug-in
  • Vue Mixins advanced components and Vue HOC advanced components practice

In both articles, the authors share their experience in packaging function stabilization/function throttling as a general-purpose component.

I won’t introduce the concept of function stabilization/function throttling here, it’s really useful to encapsulate such functionality as a component.

I also like the idea of wrapping through HOC (high-level component), and I want to share a similar approach here

Abstract component

Here I use abstract: true to create an abstract component.

The common transition and keep-alive components are abstract components. An abstract component is stateless and also “nonexistent”; it itself is not rendered as the actual DOM, but rather returns and manipulates its children.

For example, for templates (Debounce is an abstract component) :

<Debounce>
    <button>123</button>
</Debounce>
Copy the code

Will be rendered as:

<button>123</button>
Copy the code

implementation

Here we post the component code directly:

const debounce = (func, time, ctx) => {
    lettimer 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
    },
})
Copy the code

The Debounce component takes two arguments, time and events (separated by commas).

In the Render function, the Debounce component modifies the child VNode’s event and returns it.

use

Then let’s use it:

<div id="app">
    <Debounce :time="1000" events="click">
        <button @click="onClick($event1)">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>
Copy the code
const app = new Vue({
    el: '#app'.data () {
        return {
            val: 0,
        }
    },
    methods: {
        onClick ($ev, val) {
            this.val += val
        },
        onAdd () {
            this.val += 3
        }
    }
})
Copy the code

Use instruction

Using a custom directive is another option, but the bind of the directive occurs in a created callback that is later than the event’s initialization, so you can’t change the bound event callback by modifying vnode.data.on, so you have to bind the event yourself:

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)
    }
})
Copy the code

Note that the binding.value directive is evaluated differently from the event binding and does not support onClick($event, 2), so such a binding would have to be wrapped one more layer:

<button v-debounce="[($ev) => { onClick($ev, 4)}, 500]. "">click+4 {{val}}</button>
Copy the code

summary

The benefits of using abstract components are that they are more generic, don’t pollute the DOM by using components (adding unwanted div tags, etc.), can wrap arbitrary single child elements, and have disadvantages, such as the fact that child elements can only contain one root. It’s also a bit wordy to use (see article ButtonHoc is a little more concise to use, but correspondingly renders only as a Button).