1、作用
轻松使用EventListener,在挂载时使用addEventListener注册,在卸载时自动删除EventListener。
2、使用案例
未使用
我们在做事件监听时,往往需要手动注册,并且在组件卸载时,删除事件; 例如:监听页面聚焦时更新数据
js
const visibilitychangeFn = () => {
// document.hidden为false表示视图页面进入了前台
const pageVisibility = document.visibilityState;
if (pageVisibility === 'visible') {
init();
}
};
// 监听页面聚焦
onMounted(() => {
document.addEventListener('visibilitychange', visibilitychangeFn);
});
// 监听页面聚焦
onUnmounted(() => {
document.removeEventListener('visibilitychange', visibilitychangeFn);
});
使用useEventListener
js
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 作用域中调用的 vue官方文档 onScopeDispose
js
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 GeneralEventListener<E = Event> {
(evt: E): void
}
/**
* 重载1: 省略了,window目标
*/
export function useEventListener<E extends keyof WindowEventMap>(
event: Arrayable<E>,
listener: Arrayable<(this: Window, ev: WindowEventMap[E]) => any>,
options?: MaybeRefOrGetter<boolean | AddEventListenerOptions>
): Fn
/**
* 重载 2: 明确是 Window作为监听目标
*/
export function useEventListener<E extends keyof WindowEventMap>(
target: Window,
event: Arrayable<E>,
listener: Arrayable<(this: Window, ev: WindowEventMap[E]) => any>,
options?: MaybeRefOrGetter<boolean | AddEventListenerOptions>
): Fn
/**
* 重载 3: 明确是 Document作为监听目标
*/
export function useEventListener<E extends keyof DocumentEventMap>(
target: DocumentOrShadowRoot,
event: Arrayable<E>,
listener: Arrayable<(this: Window, ev: DocumentEventMap[E]) => any>,
options?: MaybeRefOrGetter<boolean | AddEventListenerOptions>
): Fn
/**
* 重载 4: 明确是 HTMLElement 作为监听目标
*/
export function useEventListener<E extends keyof HTMLElementEventMap>(
target: MaybeRefOrGetter<HTMLElement | null | undefined>,
event: Arrayable<E>,
listener: Arrayable<(this: Window, ev: HTMLElementEventMap[E]) => any>,
options?: MaybeRefOrGetter<boolean | AddEventListenerOptions>
): 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?: MaybeRefOrGetter<boolean | AddEventListenerOptions>
): Fn
/**
* 重载 6: 自定义事件降级(不推断target的类型)
*/
export function useEventListener<EventType = Event>(
target: MaybeRefOrGetter<EventTarget | null | undefined>,
event: Arrayable<string>,
listener: Arrayable<GeneralEventListener<EventType>>,
options?: MaybeRefOrGetter<boolean | AddEventListenerOptions>
): Fn
export function useEventListener(...args: any[]) {
let target: MaybeRefOrGetter<EventTarget> | undefined
let events: Arrayable<string>
let listeners: Arrayable<AnyFn>
let options: MaybeRefOrGetter<boolean | AddEventListenerOptions> | 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,我们的日常开发基本上使用不上,做了解即可;
onScopeDispose
在当前活跃的 effect 作用域上注册一个处理回调函数。当相关的 effect 作用域停止时会调用这个回调函数。
这个方法可以作为可复用的组合式函数中onUnmounted
的替代品,它并不与组件耦合,因为每一个 Vue 组件的setup()
函数也是在一个 effect 作用域中调用的
这里截取,某乎上,算是大白话,容易理解的一个评论