• 作者:老汪软件技巧
  • 发表时间:2024-12-18 10:05
  • 浏览量:

1、作用

轻松使用EventListener,在挂载时使用addEventListener注册,在卸载时自动删除EventListener。

2、使用案例

未使用

我们在做事件监听时,往往需要手动注册,并且在组件卸载时,删除事件;例如:监听页面聚焦时更新数据


const visibilitychangeFn = () => {
  // document.hidden为false表示视图页面进入了前台
  const pageVisibility = document.visibilityState;
  if (pageVisibility === 'visible') {
    init();
  }
};
// 监听页面聚焦
onMounted(() => {
  document.addEventListener('visibilitychange', visibilitychangeFn);
});
// 监听页面聚焦
onUnmounted(() => {
  document.removeEventListener('visibilitychange', visibilitychangeFn);
});

使用useEventListener

const visibilitychangeFn = () => {
  // document.hidden为false表示视图页面进入了前台
  const pageVisibility = document.visibilityState;
  if (pageVisibility === 'visible') {
    console.log('page-visible, init')
  }
};
const stopListen = useEventListener('visibilitychange', visibilitychangeFn)
// 有需要时,你也可以手动取消监听
stopListen()

3、源码解读

1、使用多个函数重载实现支持多种参数2、核心在于使用了 onScopeDispose 这个vue提供的api

这个方法可以作为可复用的组合式函数中onUnmounted的替代品,它并不与组件耦合,因为每一个 Vue 组件的setup()函数也是在一个 effect 作用域中调用的

import type { Arrayable, MaybeRefOrGetter, Fn, AnyFn, MaybeElementRef } from '../utils' 
import { noop, isObject, tryOnScopeDispose } from '../utils' 
import { watch } from 'vue'
import { unrefElement } from '../unrefElement'
import { toValue } from '../toValue'
interface InferEventTarget<Events> {
  addEventListener: (event: Events, fn?: any, options?: any) => any
  removeEventListener: (event: Events, fn?: any, options?: any) => any
}
export interface GeneralEventListenerEvent> {
  (evt: E): void
}
/**
 * 重载1: 省略了,window目标
 */
export function useEventListenerextends keyof WindowEventMap>(
  event: Arrayable,
  listener: Arrayable<(this: Window, ev: WindowEventMap[E]) => any>,
  options?: MaybeRefOrGetterAddEventListenerOptions>
): Fn 
/**
 * 重载 2: 明确是 Window作为监听目标
 */
export function useEventListenerextends keyof WindowEventMap>(
  target: Window,
  event: Arrayable,
  listener: Arrayable<(this: Window, ev: WindowEventMap[E]) => any>,
  options?: MaybeRefOrGetterAddEventListenerOptions>
): Fn 
/**
 * 重载 3: 明确是 Document作为监听目标
 */
export function useEventListenerextends keyof DocumentEventMap>(
  target: DocumentOrShadowRoot,
  event: Arrayable,
  listener: Arrayable<(this: Window, ev: DocumentEventMap[E]) => any>,
  options?: MaybeRefOrGetterAddEventListenerOptions>
): Fn 
/**
 * 重载 4: 明确是 HTMLElement 作为监听目标
 */
export function useEventListenerextends keyof HTMLElementEventMap>(
  target: MaybeRefOrGetter<HTMLElement | null | undefined>,
  event: Arrayable,
  listener: Arrayable<(this: Window, ev: HTMLElementEventMap[E]) => any>,
  options?: MaybeRefOrGetterAddEventListenerOptions>
): Fn 
/**

源码解读与原理分析__源码解读工具

* 重载 5: 自定义事件和监听目标(通过事件来推断target的类型) * EventType 是泛型,允许传入 任何 事件类型 */
export function useEventListener<Names extends string, EventType = Event>( target: MaybeRefOrGetter<InferEventTarget<Names> | null | undefined>, event: Arrayable<Names>, listener: Arrayable<GeneralEventListener<EventType>>, options?: MaybeRefOrGetterAddEventListenerOptions> ): Fn /** * 重载 6: 自定义事件降级(不推断target的类型) */ export function useEventListener<EventType = Event>( target: MaybeRefOrGetter<EventTarget | null | undefined>, event: Arrayable, listener: Arrayable<GeneralEventListener<EventType>>, options?: MaybeRefOrGetterAddEventListenerOptions> ): Fn export function useEventListener(...args: any[]) { let target: MaybeRefOrGetter<EventTarget> | undefined let events: Arrayable let listeners: Arrayable<AnyFn> let options: MaybeRefOrGetterAddEventListenerOptions> | undefined // 第一个参数是 事件类型 if (typeof args[0] === 'string' || Array.isArray(args[0])) { [events, listeners, options] = args target = window } else { // 第一个参数是 监听目标 [target, events, listeners, options] = args } // 无监听目标,无操作 if(!target) return noop; // 兼容事件和响应是 数组的情况 if (!Array.isArray(events)) events = [events] if (!Array.isArray(listeners)) listeners = [listeners] const cleanups: AnyFn[] = [] const cleanup = () => { cleanups.forEach(fn => fn()) cleanups.length = 0 } // 注册时间监听 const register = (el: any, event: string, listener: any, options: any) => { el.addEventListener(event, listener, options) return () => el.removeEventListener(event, listener, options) } const stopWatch = watch( ()=> [unrefElement(target as unknown as MaybeElementRef), toValue(options)], ([el, options]) => { cleanup() if (!el) return // 创建options的克隆,以避免在删除时被更改 const optionsClone = isObject(options) ? { ...options } : options cleanups.push( ...(events as string[]).flatMap((event) => { return (listeners as AnyFn[]).map(listener => register(el, event, listener, optionsClone)) }) ) } ) const stop = () => { stopWatch() cleanup() } tryOnScopeDispose(stop) return stop }

4、了解 effectScope、getCurrentScope、onScopeDispose

这些api,我们的日常开发基本上使用不上,做了解即可;

在当前活跃的上注册一个处理回调函数。当相关的 effect 作用域停止时会调用这个回调函数。

这个方法可以作为可复用的组合式函数中onUnmounted的替代品,它并不与组件耦合,因为每一个 Vue 组件的setup()函数也是在一个 effect 作用域中调用的

这里截取,某乎上,算是大白话,容易理解的一个评论

如何理解Vue3里的EffectScope?