最近在掘金看到两篇很是不错的文章:vue
这两篇文章中做者都分享了关于把函数防抖/函数节流包装成通用组件的经验。node
在这里我就不介绍函数防抖/函数节流的概念了,将这样的功能封装是组件真的是很是实用。bash
经过HOC(高阶组件)的方式进行封装的思路我也很喜欢,这里也想分享一个相似的封装方法app
这里我使用了abstract: true
来建立一个抽象组件。函数
咱们经常使用的transition
和keep-alive
就是一个抽象组件。抽象组件是无状态的,一样也是“不存在的”,它本身并不会被渲染为实际的DOM,而是直接返回以及操做它的子元素。post
例如对于模板(Debounce
是一个抽象组件):ui
<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
渲染)。