vue中hookEvent

2,341 阅读1分钟

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');
    });
  },