vue中hookEvent
首先抛出一个问题,我们想在一个vue文件中 监听当前页面的某个事件,可以使foucus,也可以是scroll
同时想把addEventListener和removeEventListener 放在一起 如何去解决
换句话说,如何去合并生命周期? talk is cheap show me the code
mounted() {
// 接管原生back键操作
isTakeOverBackByWeb(true);
addCustomEventListener('backKey', this.backKeyHandler);
},
destroyed() {
// 释放原生事件
isTakeOverBackByWeb(false);
removeCustomEventListener('backKey', this.backKeyHandler);
},
mounted() {
// 接管原生back键操作
isTakeOverBackByWeb(true);
addCustomEventListener('backKey', this.backKeyHandler);
this.$once('hook:destoryed', () => {
// 释放原生事件
isTakeOverBackByWeb(false);
removeCustomEventListener('backKey', this.backKeyHandler);
});
},
我们首先看下 生命周期在 vue中 是如何去调用的 ?
以运行时版本的mounted举例
Vue.prototype.$mount = function (el,hydrating) {
el = el && inBrowser ? query(el) : undefined;
return mountComponent(this, el, hydrating)
};
// 调用时机
insert (vnode: MountedComponentVNode) {
const { context, componentInstance } = vnode
if (!componentInstance._isMounted) {
componentInstance._isMounted = true
callHook(componentInstance, 'mounted')
}
if (vnode.data.keepAlive) {
if (context._isMounted) {
// vue-router#1212
// During updates, a kept-alive component's child components may
// change, so directly walking the tree here may call activated hooks
// on incorrect children. Instead we push them into a queue which will
// be processed after the whole patch process ended.
queueActivatedComponent(componentInstance)
} else {
activateChildComponent(componentInstance, true /* direct */)
}
}
},
// 定义
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中有两个调用
- invokeWithErrorHandling 通过这个函数中handler.call(context) 我们可以实现 mounted.call(vm)
- 后续有一个对于_hasHookEvent的判断 这个是什么?
// invokeWithErrorHandling(handlers[i], vm, null, vm, info)
export function invokeWithErrorHandling (
handler: Function,
context: any,
args: null | any[],
vm: any,
info: string
) {
let res
try {
res = args ? handler.apply(context, args) : handler.call(context)
if (res && !res._isVue && isPromise(res) && !res._handled) {
res.catch(e => handleError(e, vm, info + ` (Promise/async)`))
// issue #9511
// avoid catch triggering multiple times when nested calls
res._handled = true
}
} catch (e) {
handleError(e, vm, info)
}
return res
}
我去查了一下这个_hasHookEvent的来源 和变化过程 发现如下代码
- initEvents中对于_hasHookEvent 进行了赋值(init和initVirtualComponent中调用 后者会替换前者)
- 继续查找 赋值为true的情况
export function initEvents (vm: Component) {
vm._events = Object.create(null)
vm._hasHookEvent = false
// init parent attached events
const listeners = vm.$options._parentListeners
if (listeners) {
updateComponentListeners(vm, listeners)
}
}
export function eventsMixin (Vue: Class<Component>) {
const hookRE = /^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
- $once 都可以
- 通过
hook:beforeDestroy
的绑定 _hasHookEvent赋值为true, 当vue在组件执行到相应的生命周期的时候 调用callhook - callhook中 对于_hasHookEvent 做了判断,触发了emit
- 而之前我们通过on或者once 注册的事件 此时被emit触发 进行调用
- 实现了 在一个生命周期内 监听其他生命周期的功能
mounted() {
this.updateVisibleData();
// 通过hook监听组件销毁钩子函数,并取消监听事件
this.$once('hook:beforeDestroy', () => {
console.log('触发了beforeDestroy');
});
},