You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React中ref相关代码问题:点击settings时下拉菜单意外关闭+警告

React下拉导航栏问题修复:内部选项点击意外关闭 + StrictMode警告

问题现象

  1. 点击下拉菜单外部可正常关闭,但点击「settings」选项时,下拉菜单会意外关闭
  2. 控制台弹出警告:

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.25 18:48:13