设计监听浏览器事件的自定义钩子,一个浏览器关闭、返回、刷新拦截的通用方法。

525 阅读4分钟

产品有一个需求,在页面有文本框在编辑状态时,用户关闭、刷新、返回、页面跳转时要拦截用户操作,明确告知用户会丢失数据。考虑到这是一个常用功能,于是封装一个通用的组合函数来在项目中使用。

试一试

src/composable/browser-events/index.ts

/**
 * 监听浏览器自带刷新、前进、回退、关闭标签页、切换标签页
 */
const WINDOW_EVENTS = [
	/** 监听浏览器前进回退 */
	'popstate',
	/** 监听标签页关闭和刷新 */
	'beforeunload',
	/** 文档卸载 */
	'unload',
] as const;
const DOCUMENT_EVENTS = [
	/** 监听浏览器切换标签页 */
	'visibilitychange',
] as const;

// 提取联合类型
type WindowEventName = (typeof WINDOW_EVENTS)[number];
type DocumentEventName = (typeof DOCUMENT_EVENTS)[number];
type BrowserEventName = WindowEventName | DocumentEventName;

// 所有监听函数类型定义
type BrowserEventHandlers = Partial<{
	[K in BrowserEventName]: (event: Event) => void;
}>;

export const useBrowserEvents = (options: BrowserEventHandlers) => {
	const eventNames = Object.keys(options) as BrowserEventName[];

	/**
	 * 初始化
	 */
	const init = () => {
		eventNames.forEach(eventName => {
			const fn = options[eventName];
			const isFunction = typeof fn === 'function';
			if (isFunction) {
				if (WINDOW_EVENTS.includes(eventName as WindowEventName)) {
					window.addEventListener(eventName, fn);
				}
				if (DOCUMENT_EVENTS.includes(eventName as DocumentEventName)) {
					document.addEventListener(eventName, fn);
				}
			}
		});
	};

	/**
	 * 销毁
	 */
	const destroy = () => {
		eventNames.forEach(eventName => {
			const fn = options[eventName];
			const isFunction = typeof fn === 'function';
			if (isFunction) {
				if (WINDOW_EVENTS.includes(eventName as WindowEventName)) {
					window.removeEventListener(eventName, fn);
				}
				if (DOCUMENT_EVENTS.includes(eventName as DocumentEventName)) {
					document.removeEventListener(eventName, fn);
				}
			}
		});
	};
	return {
		init,
		destroy,
	};
};

export default useBrowserEvents;


使用方法

使用说明文档

概述

useBrowserEvents 是一个用于监听浏览器事件的自定义钩子。该钩子可以帮助你轻松地添加和移除对浏览器内置事件(如刷新、前进、回退、关闭标签页、切换标签页等)的监听。

导出常量

WINDOW_EVENTS

WINDOW_EVENTS 是一个常量数组,包含了需要在 window 对象上监听的事件类型:

  • 'popstate':监听浏览器前进和回退事件。
  • 'beforeunload':监听标签页关闭和刷新事件。
  • 'unload':监听文档卸载事件。

DOCUMENT_EVENTS

DOCUMENT_EVENTS 是一个常量数组,包含了需要在 document 对象上监听的事件类型:

  • 'visibilitychange':监听浏览器标签页切换事件。

使用方法

引入模块

import useBrowserEvents from '@/composable/browser-events';

调用钩子

const browserEvents = useBrowserEvents({
  popstate: () => {
    console.log('Browser history navigation occurred');
  },
  beforeunload: (event) => {
    event.preventDefault();
    event.returnValue = '';
    console.log('Browser is about to refresh or close');
  },
  unload: () => {
    console.log('Document is being unloaded');
  },
  visibilitychange: () => {
    if (document.hidden) {
      console.log('Tab is not visible');
    } else {
      console.log('Tab is visible');
    }
  }
});

初始化事件监听

browserEvents.init();

移除事件监听

browserEvents.destroy();

API 说明

useBrowserEvents

参数
  • options (Object): 一个包含事件处理函数的对象,键名为事件名称,值为对应的事件处理函数。
返回值
  • init (Function): 初始化事件监听,将指定的事件处理函数添加到相应的事件上。
  • destroy (Function): 移除事件监听,将指定的事件处理函数从相应的事件上移除。

事件类型和说明

WINDOW_EVENTS
  • 'popstate':当活动历史记录条目更改时触发。
  • 'beforeunload':在窗口、文档及其资源将要被卸载时触发。
  • 'unload':在窗口、文档及其资源即将被卸载时触发。
DOCUMENT_EVENTS
  • 'visibilitychange':当文档的可见性状态发生变化时触发。

示例

以下是一个完整的使用示例:

import useBrowserEvents from './use-browser-events'';

const browserEvents = useBrowserEvents({
  popstate: () => {
    console.log('Browser history navigation occurred');
  },
  beforeunload: (event) => {
    event.preventDefault();
    event.returnValue = '';
    console.log('Browser is about to refresh or close');
  },
  unload: () => {
    console.log('Document is being unloaded');
  },
  visibilitychange: () => {
    if (document.hidden) {
      console.log('Tab is not visible');
    } else {
      console.log('Tab is visible');
    }
  }
});

browserEvents.init();

// 当不再需要监听这些事件时
// browserEvents.destroy();

在这个示例中,我们定义了多个事件处理函数,并通过 useBrowserEvents 钩子将它们绑定到相应的事件上。通过调用 init 方法初始化事件监听,当不再需要时可以调用 destroy 方法移除这些监听。

案例

import useBrowserEvents from '@/composable/browser-events'

const popstateHandle = (e: Event) => {
    const event = e as PopStateEvent;
    alert('location: ' + document.location + ', state: ' + JSON.stringify(event.state));
};
const beforeunloadHandle = (e: Event) => {
    const event = e as BeforeUnloadEvent;
    // 判断是否有未保存的数据
    if (text.value === '') {
        return;
    }
    /** 如果有未保存的数据,就拦截浏览器默认关闭、跳转的行为 */
    if (text.value) {
        // 按照标准的规定,需要取消该事件。如果你希望阻止事件的默认行为(例如 submit、click 等),你需要调用 event.preventDefault() 或其他等效方式
        event.preventDefault();
        // Chrome 要求设置 returnValue 属性。在 Chrome 浏览器中,为了兼容某些老旧行为,必须设置 event.returnValue = false,否则事件可能不会被正确取消。这通常出现在对 beforeunload 或老版本浏览器的兼容处理代码中。
        event.returnValue = '';
    }
};

const { init, destroy } = useBrowserEvents({
    /** 浏览器地址变化 */
    popstate: popstateHandle,
    /** 拦截关闭浏览器事件 */
    beforeunload: beforeunloadHandle
})
/** 挂载时初始化 **/
onMounted(() => {
    init()
})
/** 卸载时销毁事件 */
onBeforeUnmount(() => {
    destroy()
})

参考文档

developer.mozilla.org/zh-CN/docs/… developer.mozilla.org/zh-CN/docs/…