最近在掘金看到兩篇很是不錯的文章:vue
這兩篇文章中做者都分享了關於把函數防抖/函數節流包裝成通用組件的經驗。node
在這裏我就不介紹函數防抖/函數節流的概念了,將這樣的功能封裝是組件真的是很是實用。bash
經過HOC(高階組件)的方式進行封裝的思路我也很喜歡,這裏也想分享一個相似的封裝方法markdown
這裏我使用了abstract: true
來建立一個抽象組件。app
咱們經常使用的transition
和keep-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
組件會接受time
和events
(用逗號分隔)的兩個參數。插件
在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
渲染)。