在Vue
当中,hooks
可以作为一种event
,在Vue
的源码当中,称之为hookEvent
。
<Table @hook:updated="handleTableUpdated"></Table>
项目场景
有一个来自第三方的复杂表格组件,表格进行数据更新的时候渲染时间需要1s
,由于渲染时间较长,为了更好的用户体验,我希望在表格进行更新时显示一个loading
动画。
有一个粗暴的办法是直接修改这个组件的源码,利用beforeUpdate
和updated
来显示loading
,但是这种办法非常不好
这是一个第三方的组件,作者发布组件的时候很可能对代码进行了压缩与构建,使得代码可读性很低,直接修改打包后的代码难度较高
如果有源码的话,可以fork
一份自己修改,不过作者不一定会发布源码
无法享受开源社区对这个组件的升级与维护,你需要自己手动维护
总之,修改源码这个方案可行,但是不好,不优雅。
那么,有没有办法,可以声明式的在模板上直接给一个组件注入一个生命周期函数呢?其实是可以的,就是通过hookEvent
。
生命周期函数
所谓生命周期函数,就是在某一个特定的时间点调用的函数,所以我们需要关注两件事:1、注册,2、调用。
我们首先从调用开始。
vm._self = vm
initLifecycle(vm) // 初始化生命周期
initEvents(vm) // 初始化事件
initRender(vm)
callHook(vm, 'beforeCreate')
initInjections(vm) // resolve injections before data/props
initState(vm)
initProvide(vm) // resolve provide after data/props
callHook(vm, 'created')
上面是一段Vue
的源码,可以看出,生命周期函数是通过callHook
这个函数去调用的,自然而然,我们去看看callHook
函数的代码
export function callHook (vm: Component, hook: string) {
// #7573 disable dep collection when invoking lifecycle hooks
pushTarget()
const handlers = vm.$options[hook] // 选项当中的生命周期函数
const info = `${hook} hook`
if (handlers) {
for (let i = 0, j = handlers.length; i < j; i++) {
invokeWithErrorHandling(handlers[i], vm, null, vm, info)
}
}
if (vm._hasHookEvent) {
vm.$emit('hook:' + hook)
}
popTarget()
}
所以,callHook('created')
在vm._hasHookEvent
为true
的情况下,会执行$emit('hooks:created')
,也就是说,一个有着 hook:
特殊前缀的事件,会在对应的生命周期当中执行。其实到这里,我们已经可以大胆推断了:只要使用类似@hooks:created
这个形式,就可以从Vue
的模板中声明式的注入一个生命周期函数,测试一下,it works.
<Table @hook:updated="handleTableUpdated"></Table>
hasHookEvent
之前的大胆推断,忽略了一个条件,那就是_hasHookEvent
必需为true
,接下来就去看看这个_hasHookEvent
const hookRE = /^hook:/ // 以hook:开头
Vue.prototype.$on = function (event: string | Array<string>, fn: Function): Component {
const vm: Component = this
if (Array.isArray(event)) {
for (let i = 0, l = event.length; i < l; i++) {
vm.$on(event[i], fn)
}
} else {
(vm._events[event] || (vm._events[event] = [])).push(fn)
// optimize hook:event cost by using a boolean flag marked at registration
// instead of a hash lookup
if (hookRE.test(event)) {
vm._hasHookEvent = true
}
}
return vm
}
当使用了$on
方法监听事件时,如果事件名以 hooks:
作为前缀,那么这个事件会被当做hookEvent
,注册事件回调的同时,vm._hasHookEvent
会被置为true
,当使用callHook
调用生命周期函数时,由于_hasHookEvent
为true
,所以会$emit('hooks:xxx')
,注册的生命周期函数就会执行。
所以,如果你想给一个Vue
组件添加生命周期函数有3个办法:
- 在
Vue
组件选项中添加; - 在模板中通过
@hooks:created
这种形式; vm.$on('hooks:created', cb)
或者vm.$once('hooks:created', cb)
。