React可复用下拉组件问题:点击任意下拉按钮或外部元素时所有下拉菜单同时展开/收起
React可复用下拉组件问题:点击任意下拉按钮或外部元素时所有下拉菜单同时展开/收起
嘿,我仔细瞅了你的代码和问题描述,发现几个关键问题导致所有下拉菜单同时触发,咱们一步步来搞定:
问题根源分析
- watchElements传递错误:你在循环渲染Dropdown时,给每个Dropdown传了
containerRefs.current——这可是所有容器的ref数组啊!也就是说,每个Dropdown实例都会给所有容器元素绑定点击事件,点击任意一个容器,所有Dropdown的切换逻辑都会跑一遍,可不就全动了嘛。 - toggleDropdown逻辑搞反了:当前的toggle函数只判断点击的元素是否在下拉组件内部,但你要的是点击对应的外部容器(或者按钮)时切换自己的Dropdown,这个判断逻辑完全不对路。
- 按钮点击事件被注释了:你把Button的
onClick={toggleDropdown}注释掉了,导致点击按钮本身根本不会触发切换,这肯定不是你想要的效果。
修复步骤
第一步:修正watchElements的传递
在循环里,每个Dropdown只需要传递当前对应的那个容器ref,而不是整个数组:
{constants.home_services.map((service, index) => ( <div className="w-full relative" key={index} ref={(el) => (containerRefs.current[index] = el)} > {/* ... 中间的标题div ... */} <Dropdown // 只传当前索引对应的ref,别传整个数组! watchElements={[containerRefs.current[index]]} // ... 其他props保持不变 ... > {/* ... 下拉内容 ... */} </Dropdown> </div> ))}
第二步:修正toggleDropdown的逻辑
咱们改一下toggle函数:当点击的是组件内部的按钮,或者是watchElements里的外部元素时,才切换当前Dropdown的状态,还要阻止事件冒泡避免重复触发:
import { useState, useEffect, useRef, useCallback } from "react"; // 别忘了引入useCallback // 用useCallback包裹,避免每次渲染重新创建函数 const toggleDropdown = useCallback((event) => { event.stopPropagation(); // 阻止事件冒泡,避免触发外部的点击关闭逻辑 // 判断:点击的是按钮/下拉内部,或者是watchElements里的外部元素 const isClickOnSelf = dropdownRef.current?.contains(event.target); const isClickOnWatchElement = watchElements.some( el => el?.contains(event.target) ); if (isClickOnSelf || isClickOnWatchElement) { setIsOpen(prev => !prev); } }, [watchElements]);
第三步:恢复Button的点击事件
把Button的onClick绑回去,这样点击按钮也能切换下拉:
<Button className={`${btn_classes} mt-3 flex items-center justify-center w-full`} onClick={toggleDropdown} // 把这个点击事件恢复回来 isDefault={false} > {btn_child} </Button>
第四步:优化useEffect的依赖和清理逻辑
确保useEffect的依赖正确,并且清理事件监听时不会出错:
useEffect(() => { document.addEventListener("mousedown", handleClickOutside); // 给watchElements里的每个元素绑定事件 watchElements.forEach(watchElement => { watchElement?.addEventListener("click", toggleDropdown); }); return () => { document.removeEventListener("mousedown", handleClickOutside); // 清理每个元素的事件监听 watchElements.forEach(watchElement => { watchElement?.removeEventListener("click", toggleDropdown); }); }; }, [watchElements, toggleDropdown]); // 把toggleDropdown加入依赖
最后测试一下
现在你点击任意一个容器或者它的下拉按钮,应该只有对应的下拉菜单会展开/收起,其他的完全不受影响。如果还有问题,检查一下ref是否正确绑定,以及watchElements是否只传递了当前的容器ref就行。
备注:内容来源于stack exchange,提问作者Mohsin Hassan Khan
相关产品推荐
相关产品推荐

