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
							parent
							
								
									2f7d1f009d
								
							
						
					
					
						commit
						e7fd0e3b6a
					
				|  | @ -2,7 +2,7 @@ import type { Arrayable, MaybeElementRef } from '@vueuse/core'; | |||
| 
 | ||||
| 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'; | ||||
| 
 | ||||
|  | @ -20,12 +20,12 @@ const DEFAULT_ENTER_DELAY = 0; // 鼠标进入延迟时间,默认为 0(立 | |||
| 
 | ||||
| /** | ||||
|  * 监测鼠标是否在元素内部,如果在元素内部则返回 true,否则返回 false | ||||
|  * @param refElement 所有需要检测的元素。如果提供了一个数组,那么鼠标在任何一个元素内部都会返回 true | ||||
|  * @param refElement 所有需要检测的元素。支持单个元素、元素数组或响应式引用的元素数组。如果鼠标在任何一个元素内部都会返回 true | ||||
|  * @param delay 延迟更新状态的时间,可以是数字或包含进入/离开延迟的配置对象 | ||||
|  * @returns 返回一个数组,第一个元素是一个 ref,表示鼠标是否在元素内部,第二个元素是一个控制器,可以通过 enable 和 disable 方法来控制监听器的启用和禁用 | ||||
|  */ | ||||
| export function useHoverToggle( | ||||
|   refElement: Arrayable<MaybeElementRef>, | ||||
|   refElement: Arrayable<MaybeElementRef> | Ref<HTMLElement[] | null>, | ||||
|   delay: (() => number) | HoverDelayOptions | number = DEFAULT_LEAVE_DELAY, | ||||
| ) { | ||||
|   // 兼容旧版本API
 | ||||
|  | @ -38,20 +38,58 @@ export function useHoverToggle( | |||
|           ...delay, | ||||
|         }; | ||||
| 
 | ||||
|   const isHovers: Array<Ref<boolean>> = []; | ||||
|   const value = ref(false); | ||||
|   const enterTimer = ref<ReturnType<typeof setTimeout> | undefined>(); | ||||
|   const leaveTimer = ref<ReturnType<typeof setTimeout> | undefined>(); | ||||
|   const refs = Array.isArray(refElement) ? refElement : [refElement]; | ||||
|   refs.forEach((refEle) => { | ||||
|   const hoverScopes = ref<ReturnType<typeof effectScope>[]>([]); | ||||
| 
 | ||||
|   // 使用计算属性包装 refElement,使其响应式变化
 | ||||
|   const refs = computed(() => { | ||||
|     const raw = unref(refElement); | ||||
|     if (raw === null) return []; | ||||
|     return Array.isArray(raw) ? raw : [raw]; | ||||
|   }); | ||||
|   // 存储所有 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 isHover = useElementHover(eleRef); | ||||
|     isHovers.push(isHover); | ||||
| 
 | ||||
|       // 为每个元素创建独立的作用域
 | ||||
|       const scope = effectScope(); | ||||
|       const hoverRef = scope.run(() => useElementHover(eleRef)) || ref(false); | ||||
|       hoverScopes.value.push(scope); | ||||
| 
 | ||||
|       return hoverRef; | ||||
|     }); | ||||
|   const isOutsideAll = computed(() => isHovers.every((v) => !v.value)); | ||||
|   } | ||||
| 
 | ||||
|   // 监听元素数量变化,避免过度执行
 | ||||
|   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() { | ||||
|     if (enterTimer.value) { | ||||
|  | @ -96,7 +134,7 @@ export function useHoverToggle( | |||
|     } | ||||
|   } | ||||
| 
 | ||||
|   const watcher = watch( | ||||
|   const hoverWatcher = watch( | ||||
|     isOutsideAll, | ||||
|     (val) => { | ||||
|       setValueDelay(!val); | ||||
|  | @ -106,15 +144,19 @@ export function useHoverToggle( | |||
| 
 | ||||
|   const controller = { | ||||
|     enable() { | ||||
|       watcher.resume(); | ||||
|       hoverWatcher.resume(); | ||||
|     }, | ||||
|     disable() { | ||||
|       watcher.pause(); | ||||
|       hoverWatcher.pause(); | ||||
|     }, | ||||
|   }; | ||||
| 
 | ||||
|   onUnmounted(() => { | ||||
|     clearTimers(); | ||||
|     // 停止监听器
 | ||||
|     stopWatcher(); | ||||
|     // 停止所有剩余的作用域
 | ||||
|     hoverScopes.value.forEach((scope) => scope.stop()); | ||||
|   }); | ||||
| 
 | ||||
|   return [value, controller] as [typeof value, typeof controller]; | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	 broBinChen
						broBinChen