React中ref相关代码问题:点击settings时下拉菜单意外关闭+警告
React下拉导航栏问题修复:内部选项点击意外关闭 + StrictMode警告
问题现象
- 点击下拉菜单外部可正常关闭,但点击「settings」选项时,下拉菜单会意外关闭
- 控制台弹出警告:
Warning: findDOMNode is deprecated in StrictMode. findDOMNode was passed an instance of Transition which is inside StrictMode. Instead, add a ref directly to the element you want to reference.
问题分析
1. 内部点击关闭下拉的原因
点击「settings」时,先触发菜单切换逻辑(卸载原菜单、挂载新菜单),事件冒泡到document的点击监听时,原「settings」按钮已被移除出DOM树,导致navItemRef.current.contains(evt.target)返回false,触发setOpen(false)关闭下拉。
2. StrictMode警告的原因
react-transition-group的CSSTransition默认使用findDOMNode获取过渡元素,而React StrictMode已废弃该API,需手动传入元素ref替代。
修复方案
一、修复内部点击关闭下拉的问题
方案1:使用事件路径判断(推荐)
修改NavItem中的外部点击监听逻辑,通过composedPath()获取完整点击路径(即使元素已被移除,也能判断触发时的归属):
// 替换原NavItem中的useEffect代码 useEffect(() => { const handleOutsideClick = (evt: MouseEvent) => { const clickPath = evt.composedPath(); if (!clickPath.includes(navItemRef.current!)) { setOpen(false); } }; document.addEventListener("click", handleOutsideClick); return () => { document.removeEventListener("click", handleOutsideClick); }; }, []);
方案2:阻止内部事件冒泡
在DropDownItem的点击事件中阻止冒泡,避免触发document层级的监听:
// 修改DropdownMenu中的DropDownItem组件 const DropDownItem: React.FC<IDropDownItemProps> = ({ toMenu, leftIcon, rightIcon, children, }) => { const handleClick = (e: React.MouseEvent<HTMLAnchorElement>) => { e.stopPropagation(); // 阻止事件冒泡到document if (toMenu) setActiveMenu(toMenu); }; return ( <a href="#" className={styles["menu-item"]} onClick={handleClick} > <span className={styles["icon-button"]}>{leftIcon}</span> {children} <span className={styles["icon-right"]}>{rightIcon}</span> </a> ); };
二、修复StrictMode警告
给每个CSSTransition添加nodeRef属性,手动传入过渡元素的ref:
// 修改DropdownMenu组件 const DropdownMenu: React.FC = () => { const [activeMenu, setActiveMenu] = useState<DropDownItemType>("main"); const [menuHeight, setMenuHeight] = useState<number>(0); const dropdownRef = useRef<HTMLDivElement>(null); // 新增两个ref指向菜单容器 const mainMenuRef = useRef<HTMLDivElement>(null); const settingsMenuRef = useRef<HTMLDivElement>(null); useEffect(() => { const firstChild = dropdownRef.current?.firstChild as HTMLDivElement; setMenuHeight(firstChild?.offsetHeight); }, []); const calcHeight = (el: HTMLElement) => { const height = el.offsetHeight; setMenuHeight(height); }; // DropDownItem组件代码不变 return ( <div className={styles.dropdown} style={{ height: menuHeight }} ref={dropdownRef} > <CSSTransition in={activeMenu === "main"} timeout={500} classNames={{ enter: styles["menu-primary-enter"], enterActive: styles["menu-primary-enter-active"], exit: styles["menu-primary-exit"], exitActive: styles["menu-primary-exit-active"], }} unmountOnExit onEnter={calcHeight} nodeRef={mainMenuRef} // 绑定nodeRef > <div className={styles.menu} ref={mainMenuRef}> {/* 绑定ref到菜单容器 */} <DropDownItem>profile</DropDownItem> <DropDownItem leftIcon={"icon1"} rightIcon={"icon1"} toMenu="settings"> settings </DropDownItem> </div> </CSSTransition> <CSSTransition in={activeMenu === "settings"} timeout={500} classNames={{ enter: styles["menu-secondary-enter"], enterActive: styles["menu-secondary-enter-active"], exitActive: styles["menu-secondary-exit-active"], }} unmountOnExit onEnter={calcHeight} nodeRef={settingsMenuRef} // 绑定nodeRef > <div className={styles.menu} ref={settingsMenuRef}> {/* 绑定ref到菜单容器 */} <DropDownItem toMenu="main" leftIcon={"😥"}> <h2>Back</h2> </DropDownItem> <DropDownItem leftIcon={"icon3"}>setting 1</DropDownItem> <DropDownItem leftIcon={"icon3"}>setting 2</DropDownItem> <DropDownItem leftIcon={"icon3"}>setting 3</DropDownItem> </div> </CSSTransition> </div> ); };
额外优化建议
给DropDownItem的<a>标签添加e.preventDefault(),避免点击时页面跳转到顶部:
const handleClick = (e: React.MouseEvent<HTMLAnchorElement>) => { e.preventDefault(); e.stopPropagation(); if (toMenu) setActiveMenu(toMenu); };
内容的提问来源于stack exchange,提问作者Libra
相关产品推荐
相关产品推荐

