Skip to content

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

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: main
Choose a base branch
from

Conversation

broBinChen
Copy link
Contributor

@broBinChen broBinChen commented Jun 4, 2025

Description

在使用 useHoverToggle hook 监听一组 DOM 元素的 hover 事件时,我遇到了一个问题:这些元素在运行时会动态更新,但新增的元素缺失了 hover 监听器,导致行为不一致。

PixPin_2025-06-04_16-18-04_before.mp4

解决方案

本PR通过为 useHoverToggle hook 增加对 refElement 参数的响应式支持来解决这个问题。主要改动包括:

  • 扩展类型支持:在参数类型中添加了 Ref<HTMLElement[] | null>,支持响应式元素引用
  • 响应式元素追踪:实现了当元素引用发生变化时的自动更新机制
  • 动态监听器管理:添加了 updateHovers() 函数和 watch 机制来处理动态DOM变化
  • 空值处理:通过正确处理元素引用中的空值来提高代码健壮性
PixPin_2025-06-04_16-18-50_after.mp4

Type of change

Please delete options that are not relevant.

  • New feature (non-breaking change which adds functionality)

Checklist

ℹ️ Check all checkboxes - this will indicate that you have done everything in accordance with the rules in CONTRIBUTING.

  • If you introduce new functionality, document it. You can run documentation with pnpm run docs:dev command.
  • Run the tests with pnpm test.
  • Changes in changelog are generated from PR name. Please, make sure that it explains your changes in an understandable manner. Please, prefix changeset messages with feat:, fix:, perf:, docs:, or chore:.
  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • Any dependent changes have been merged and published in downstream modules

Summary by CodeRabbit

  • Refactor
    • Enhanced hover toggle behavior to support reactive arrays of elements, improving responsiveness and accuracy when tracking hover states across multiple or changing elements.

Copy link

changeset-bot bot commented Jun 4, 2025

⚠️ No Changeset found

Latest commit: 741286a

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

Copy link
Contributor

coderabbitai bot commented Jun 4, 2025

Walkthrough

The useHoverToggle hook was updated to support more flexible input types for its refElement parameter, now accepting both direct and reactive references to arrays of elements. The internal logic was refactored to handle these inputs reactively, ensuring hover tracking updates dynamically as the referenced elements change.

Changes

File(s) Change Summary
packages/effects/hooks/src/use-hover-toggle.ts Expanded refElement parameter types to include reactive arrays; refactored hover tracking to use reactive refs, effect scopes, and dynamic updates on element changes.

Possibly related PRs

Suggested reviewers

  • jinmao88
  • mynetfan
  • anncwb
  • vince292007

Poem

In the meadow of code, where hovers reside,
A toggle now listens as elements glide.
Arrays or refs, all welcomed inside,
With dynamic updates, it tracks every side.
The rabbit hops by, with a satisfied cheer—
"Your hover is smarter, no bugs to fear!" 🐇✨


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2ff17ff and 62726a0.

📒 Files selected for processing (1)
  • packages/effects/hooks/src/use-hover-toggle.ts (5 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/effects/hooks/src/use-hover-toggle.ts
⏰ Context from checks skipped due to timeout of 90000ms (8)
  • GitHub Check: Test (windows-latest)
  • GitHub Check: Check (ubuntu-latest)
  • GitHub Check: Check (windows-latest)
  • GitHub Check: Lint (windows-latest)
  • GitHub Check: Lint (ubuntu-latest)
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: post-update (ubuntu-latest)
  • GitHub Check: post-update (windows-latest)
✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
packages/effects/hooks/src/use-hover-toggle.ts (2)

11-16: Update documentation to reflect reactive array support.

The JSDoc comment should be updated to mention that refElement now also accepts reactive references to arrays of elements.

 /**
  * 监测鼠标是否在元素内部,如果在元素内部则返回 true,否则返回 false
- * @param refElement 所有需要检测的元素。如果提供了一个数组,那么鼠标在任何一个元素内部都会返回 true
+ * @param refElement 所有需要检测的元素。支持单个元素、元素数组或响应式引用的元素数组。如果鼠标在任何一个元素内部都会返回 true
  * @param delay 延迟更新状态的时间
  * @returns 返回一个数组,第一个元素是一个 ref,表示鼠标是否在元素内部,第二个元素是一个控制器,可以通过 enable 和 disable 方法来控制监听器的启用和禁用
  */

47-47: Consider the performance impact of deep watching.

The deep: true option might cause unnecessary re-executions if array element references change without actual element changes. Since refs is already reactive, consider if deep watching is truly necessary.

If the array elements themselves don't change internally (only the array composition changes), you could remove the deep option:

-  watch(refs, updateHovers, { deep: true });
+  watch(refs, updateHovers);

Alternatively, if deep watching is necessary, consider using a more targeted approach to minimize performance impact.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b9aef61 and 0db2049.

📒 Files selected for processing (1)
  • packages/effects/hooks/src/use-hover-toggle.ts (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: post-update (ubuntu-latest)
  • GitHub Check: post-update (windows-latest)
🔇 Additional comments (2)
packages/effects/hooks/src/use-hover-toggle.ts (2)

17-20: Good type extension for reactive array support!

The parameter type extension maintains backward compatibility while adding the requested reactive array functionality.


33-47:

❓ Verification inconclusive

Critical: Memory leak - old hover listeners are not cleaned up.

When updateHovers is called due to refs changes, new useElementHover instances are created without cleaning up the old ones. This causes memory leaks as the old event listeners remain active.

Each useElementHover call sets up event listeners that need to be properly disposed when no longer needed.

Consider implementing a cleanup mechanism:

+  // Store cleanup functions
+  const cleanupFns = ref<Array<() => void>>([]);
+
   // 更新 hover 监听的函数
   function updateHovers() {
+    // Clean up old listeners
+    cleanupFns.value.forEach(fn => fn());
+    cleanupFns.value = [];
+    
     isHovers.value = refs.value.map((refEle) => {
       const eleRef = computed(() => {
         const ele = unref(refEle);
         return ele instanceof Element ? ele : (ele?.$el as Element);
       });
-      return useElementHover(eleRef);
+      const hoverRef = useElementHover(eleRef);
+      // Store cleanup function if the composable provides one
+      // Note: Check VueUse documentation for the actual cleanup mechanism
+      return hoverRef;
     });
   }

   onUnmounted(() => {
     timer.value && clearTimeout(timer.value);
+    // Clean up all listeners on unmount
+    cleanupFns.value.forEach(fn => fn());
   });

Let me verify how VueUse handles cleanup for useElementHover:


🏁 Script executed:

#!/bin/bash
# Description: Check how useElementHover handles cleanup in VueUse

# Search for useElementHover implementation to understand its cleanup mechanism
fd -e ts -e js | xargs rg -A 10 "export.*function.*useElementHover" 

# Also check if it uses watchEffect or other Vue APIs that auto-cleanup
fd -e ts -e js | xargs rg -A 5 "useElementHover.*watchEffect|useElementHover.*onScopeDispose"

Length of output: 165


Critical: Memory leak in updateHovers – old hover listeners are never disposed

When updateHovers runs (e.g. on refs changes), it calls useElementHover again without tearing down the previous instances. Because each call registers event listeners tied to the component’s effect scope, you’ll accumulate listeners until the component unmounts, leading to memory leaks and unexpected behavior.

Proposed cleanup via isolated effect scopes:

• Add at the top of your hook:
const hoverScopes = ref<EffectScope[]>([])
• In updateHovers(), before mapping new hovers:
– Stop and clear previous scopes:
hoverScopes.value.forEach(s => s.stop()); hoverScopes.value = []
• For each element:
const scope = effectScope()
const hoverRef = scope.run(() => useElementHover(eleRef))
hoverScopes.value.push(scope)
– return hoverRef
• In onUnmounted(), stop any remaining scopes:
hoverScopes.value.forEach(s => s.stop())

Example diff:

+ import { ref, computed, watch, onUnmounted, unref, effectScope, EffectScope } from 'vue';
  import { useElementHover } from '@vueuse/core';
 
+ const hoverScopes = ref<EffectScope[]>([]);
 
   function updateHovers() {
+    // clean up old hover listeners
+    hoverScopes.value.forEach(scope => scope.stop());
+    hoverScopes.value = [];
 
     isHovers.value = refs.value.map((refEle) => {
+      const scope = effectScope();
+      const hoverRef = scope.run(() => {
+        const eleRef = computed(() => {
+          const ele = unref(refEle);
+          return ele instanceof Element ? ele : (ele?.$el as Element);
+        });
+        return useElementHover(eleRef);
+      });
+      hoverScopes.value.push(scope);
+      return hoverRef;
     });
   }
 
   onUnmounted(() => {
     timer.value && clearTimeout(timer.value);
+    // ensure all listener scopes are stopped
+    hoverScopes.value.forEach(scope => scope.stop());
   });

Please verify whether useElementHover already provides a built-in disposal (e.g. via onScopeDispose) or if the above scope-based cleanup is required.

@jinmao88
Copy link
Collaborator

jinmao88 commented Jun 8, 2025

解决一下ai反馈的问题和冲突

broBinChen and others added 2 commits June 9, 2025 10:15
…watch 监听深度,仅需浅层监听 refs 变化。 3、使用 effectScope 管理 useElementHover 实例,避免 refs 变化时事件监听器累积导致的内存泄漏问题
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (3)
packages/effects/hooks/src/use-hover-toggle.ts (3)

53-53: Consider the ref structure complexity.

While the nested ref structure (ref<Array<Ref<boolean>>>) works functionally, it creates a complex reactive structure that might be harder to reason about. However, this appears necessary for the reactive behavior requirements.

Consider adding a comment explaining why this nested ref structure is necessary for maintainability.


56-74: Enhance error handling in updateHovers function.

The implementation correctly manages effect scopes and hover listeners, but could benefit from additional error handling for edge cases.

Consider adding error handling for potential issues:

 function updateHovers() {
   // 停止并清理之前的作用域
   hoverScopes.value.forEach((scope) => scope.stop());
   hoverScopes.value = [];

-  isHovers.value = refs.value.map((refEle) => {
+  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;
   });
 }

77-79: Optimize the watch configuration.

The deep watch might be unnecessary and could impact performance. Consider if a shallow watch would suffice for your use case.

-watch(refs, updateHovers, { deep: 1 });
+watch(refs, updateHovers, { deep: false });

Since refs is a computed that returns a new array when dependencies change, a shallow watch should be sufficient. Test this change to ensure it still triggers appropriately for your use cases.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7c33109 and 2ff17ff.

📒 Files selected for processing (1)
  • packages/effects/hooks/src/use-hover-toggle.ts (4 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (8)
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: Check (windows-latest)
  • GitHub Check: Check (ubuntu-latest)
  • GitHub Check: Lint (ubuntu-latest)
  • GitHub Check: Test (windows-latest)
  • GitHub Check: Lint (windows-latest)
  • GitHub Check: post-update (ubuntu-latest)
  • GitHub Check: post-update (windows-latest)
🔇 Additional comments (6)
packages/effects/hooks/src/use-hover-toggle.ts (6)

5-5: LGTM: Import addition is appropriate.

The effectScope import is correctly added to support the new reactive effect management functionality.


23-23: LGTM: Type signature and documentation updates are accurate.

The function signature correctly extends support to Ref<HTMLElement[] | null> and the JSDoc documentation accurately reflects the new reactive capabilities.

Also applies to: 28-28


44-44: LGTM: Effect scopes tracking is properly initialized.

Using a ref to store effect scopes is appropriate for tracking and cleanup purposes.


46-51: LGTM: Input normalization is well-implemented.

The computed property properly handles the different input types (null, single element, array) and provides reactive unwrapping of the refElement parameter.


81-81: LGTM: Computed property correctly uses reactive array.

The isOutsideAll computed property properly references isHovers.value to maintain reactivity.


145-146: LGTM: Proper cleanup on unmount.

The cleanup logic correctly stops all effect scopes to prevent memory leaks.

@broBinChen
Copy link
Contributor Author

@coderabbitai I pushed a fix in commit 62726a0, please review it.

Copy link
Contributor

coderabbitai bot commented Jun 9, 2025

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Examine the specific commit 62726a0 to see what changes were made
git show --stat 62726a0c01d81b0adf4d3ba7dc1b509db4241ae9

# Get the detailed diff for this commit
git show 62726a0c01d81b0adf4d3ba7dc1b509db4241ae9

# Check the current state of the useHoverToggle file
cat packages/effects/hooks/src/use-hover-toggle.ts

Length of output: 69711


🏁 Script executed:

#!/bin/bash
# Get the full content of the use-hover-toggle.ts file to see the current implementation
cat packages/effects/hooks/src/use-hover-toggle.ts

# Check if there are any remaining merge conflicts
grep -r "<<<<<<< HEAD\|=======" . --include="*.ts" --include="*.vue" --include="*.js" --include="*.json" || echo "No merge conflicts found"

# Check the status of the repository to see if there are any uncommitted changes
git status --porcelain

Length of output: 4630


@broBinChen Great work on the fixes in commit 62726a0! I've reviewed the changes and they look excellent. Here's what I found:

✅ Improvements Made

Performance Optimization:

  • Smart watching strategy: Changed from watching the entire refs array to only watching elementsCount, which is much more efficient and avoids unnecessary re-execution when array contents change but count remains the same
  • Proper scope management: Enhanced cleanup of effect scopes to prevent memory leaks

Enhanced Boundary Handling:

  • Null safety: Added proper null checking in the refs computed property (if (raw === null) return [])
  • Robust element handling: Better handling of edge cases when elements are null or undefined

Code Quality:

  • Clean lifecycle management: Proper cleanup of watchers and effect scopes in onUnmounted
  • Clear separation of concerns: The updateHovers function is well-structured and handles scope cleanup properly

✅ Issues Resolved

  • ✅ No merge conflicts detected
  • ✅ Repository status is clean
  • ✅ Performance optimization successfully implemented
  • ✅ Memory leak prevention through proper cleanup

📝 Note on Changeset

The changeset-bot mentioned no changeset was found. This might need to be added by a maintainer if this change should result in a version bump for the @vben/hooks package.

The optimization you've implemented (只监听元素数量变化而不是整个数组变化,避免过度依赖收集) is exactly the right approach for this use case. Well done! 🎉

🐇✨ The rabbit hops with joy seeing such clean, optimized code!


If you found this review helpful, would you consider giving us a shout-out on X?

Thank you for using CodeRabbit!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants