feat(hooks): useHoverToggle的入参refElement支持传入响应式数组 (#6333)

* feat(hooks): useHoverToggle的入参refElement支持传入响应式数组

* feat(hooks): 1、增加 useHoverToggle 中 refElement 参数关于传入响应式数组的注释说明。 2、修改 watch 监听深度,仅需浅层监听 refs 变化。 3、使用 effectScope 管理 useElementHover 实例,避免 refs 变化时事件监听器累积导致的内存泄漏问题

* feat(hooks): 在useHoverToggle中增强 updateHovers  的边界处理,优化watch方案,只监听元素数量变化而不是整个数组变化,避免过度依赖收集

---------

Co-authored-by: xiaobin <xiaobin_chen@fzzixun.com>
pull/162/head^2^2
broBinChen 2025-06-27 19:08:41 +08:00 committed by GitHub
parent 2f7d1f009d
commit e7fd0e3b6a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 58 additions and 16 deletions

View File

@ -2,7 +2,7 @@ import type { Arrayable, MaybeElementRef } from '@vueuse/core';
import type { Ref } from 'vue'; import type { Ref } from 'vue';
import { computed, onUnmounted, ref, unref, watch } from 'vue'; import { computed, effectScope, onUnmounted, ref, unref, watch } from 'vue';
import { isFunction } from '@vben/utils'; import { isFunction } from '@vben/utils';
@ -20,12 +20,12 @@ const DEFAULT_ENTER_DELAY = 0; // 鼠标进入延迟时间,默认为 0
/** /**
* true false * true false
* @param refElement true * @param refElement true
* @param delay / * @param delay /
* @returns ref enable disable * @returns ref enable disable
*/ */
export function useHoverToggle( export function useHoverToggle(
refElement: Arrayable<MaybeElementRef>, refElement: Arrayable<MaybeElementRef> | Ref<HTMLElement[] | null>,
delay: (() => number) | HoverDelayOptions | number = DEFAULT_LEAVE_DELAY, delay: (() => number) | HoverDelayOptions | number = DEFAULT_LEAVE_DELAY,
) { ) {
// 兼容旧版本API // 兼容旧版本API
@ -38,20 +38,58 @@ export function useHoverToggle(
...delay, ...delay,
}; };
const isHovers: Array<Ref<boolean>> = [];
const value = ref(false); const value = ref(false);
const enterTimer = ref<ReturnType<typeof setTimeout> | undefined>(); const enterTimer = ref<ReturnType<typeof setTimeout> | undefined>();
const leaveTimer = ref<ReturnType<typeof setTimeout> | undefined>(); const leaveTimer = ref<ReturnType<typeof setTimeout> | undefined>();
const refs = Array.isArray(refElement) ? refElement : [refElement]; const hoverScopes = ref<ReturnType<typeof effectScope>[]>([]);
refs.forEach((refEle) => {
const eleRef = computed(() => { // 使用计算属性包装 refElement使其响应式变化
const ele = unref(refEle); const refs = computed(() => {
return ele instanceof Element ? ele : (ele?.$el as Element); const raw = unref(refElement);
}); if (raw === null) return [];
const isHover = useElementHover(eleRef); return Array.isArray(raw) ? raw : [raw];
isHovers.push(isHover);
}); });
const isOutsideAll = computed(() => isHovers.every((v) => !v.value)); // 存储所有 hover 状态
const isHovers = ref<Array<Ref<boolean>>>([]);
// 更新 hover 监听的函数
function updateHovers() {
// 停止并清理之前的作用域
hoverScopes.value.forEach((scope) => scope.stop());
hoverScopes.value = [];
isHovers.value = refs.value.map((refEle) => {
if (!refEle) {
return ref(false);
}
const eleRef = computed(() => {
const ele = unref(refEle);
return ele instanceof Element ? ele : (ele?.$el as Element);
});
// 为每个元素创建独立的作用域
const scope = effectScope();
const hoverRef = scope.run(() => useElementHover(eleRef)) || ref(false);
hoverScopes.value.push(scope);
return hoverRef;
});
}
// 监听元素数量变化,避免过度执行
const elementsCount = computed(() => {
const raw = unref(refElement);
if (raw === null) return 0;
return Array.isArray(raw) ? raw.length : 1;
});
// 初始设置
updateHovers();
// 只在元素数量变化时重新设置监听器
const stopWatcher = watch(elementsCount, updateHovers, { deep: false });
const isOutsideAll = computed(() => isHovers.value.every((v) => !v.value));
function clearTimers() { function clearTimers() {
if (enterTimer.value) { if (enterTimer.value) {
@ -96,7 +134,7 @@ export function useHoverToggle(
} }
} }
const watcher = watch( const hoverWatcher = watch(
isOutsideAll, isOutsideAll,
(val) => { (val) => {
setValueDelay(!val); setValueDelay(!val);
@ -106,15 +144,19 @@ export function useHoverToggle(
const controller = { const controller = {
enable() { enable() {
watcher.resume(); hoverWatcher.resume();
}, },
disable() { disable() {
watcher.pause(); hoverWatcher.pause();
}, },
}; };
onUnmounted(() => { onUnmounted(() => {
clearTimers(); clearTimers();
// 停止监听器
stopWatcher();
// 停止所有剩余的作用域
hoverScopes.value.forEach((scope) => scope.stop());
}); });
return [value, controller] as [typeof value, typeof controller]; return [value, controller] as [typeof value, typeof controller];