以下是手写 VueUse 的 onClickOutside
函数的实现,采用 Vue 3 组合式 API 风格,包含详细注释和代码:
typescript
import { Ref, onMounted, onUnmounted } from 'vue';
/**
* 检测点击目标元素外部的操作
* @param targetRef 要监听的目标元素引用
* @param callback 点击外部时触发的回调函数
*/
export function onClickOutside(
targetRef: Ref<HTMLElement | null>,
callback: (event: MouseEvent) => void
) {
// 事件处理函数
const listener = (event: MouseEvent) => {
const target = targetRef.value;
// 确保目标元素存在且事件目标存在
if (!target || !event.target) return;
// 判断点击是否发生在目标元素外部
if (!target.contains(event.target as Node)) {
callback(event);
}
};
// 组件挂载时添加监听
onMounted(() => {
// 使用捕获阶段确保在阻止冒泡的情况下仍能触发
document.addEventListener('click', listener, { capture: true });
});
// 组件卸载时移除监听
onUnmounted(() => {
document.removeEventListener('click', listener, { capture: true });
});
}
使用示例
vue
<script setup lang="ts">
import { ref } from 'vue';
import { onClickOutside } from './onClickOutside';
const target = ref<HTMLElement | null>(null);
const isOpen = ref(true);
onClickOutside(target, () => {
isOpen.value = false;
});
</script>
<template>
<div v-if="isOpen" ref="target" class="modal">
点击外部区域关闭我
</div>
</template>
实现要点解析
-
事件阶段处理:
- 使用
{ capture: true }
在捕获阶段监听事件,确保即使其他事件调用了stopPropagation()
也能触发
- 使用
-
类型安全:
- 使用 TypeScript 类型标注,明确处理
HTMLElement
和事件对象类型
- 使用 TypeScript 类型标注,明确处理
-
内存管理:
- 通过
onUnmounted
自动清理事件监听,避免内存泄漏
- 通过
-
边界情况处理:
- 检查目标元素是否存在(
targetRef.value
) - 检查事件目标是否存在(
event.target
)
- 检查目标元素是否存在(
-
DOM 判断逻辑:
- 使用
contains()
方法判断点击目标是否在监听元素内部
- 使用
扩展功能建议
如需实现更复杂的功能,可以考虑添加以下特性:
- 排除元素:允许配置不触发回调的排除元素
- 事件类型 :支持监听其他事件类型(如
mousedown
) - 响应式配置:通过响应式参数控制监听行为
- 立即触发:添加 immediate 选项支持初始状态检测
- 条件判断:添加执行回调的条件判断函数