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