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

动态导航菜单超时函数未正常清除问题排查

导航菜单下拉菜单超时未取消问题修复

我开发了一款带下拉菜单的导航组件,当用户hover菜单项时,会更新中央的NavListDropdown内容并显示。预期逻辑是:用户离开菜单项时设置1.5秒超时隐藏下拉;若用户再次hover任意菜单项,取消该超时。但快速切换hover时,之前的超时未被取消,导致下拉菜单出现异常。

原组件代码

const Nav = (props) => {
  const [isItemHovered, setIsItemHovered] = useState(false);
  const [isDropdownVisible, setIsDropdownVisible] = useState(false);
  const [dropdownContent, setDropdownContent] = useState(null);
  const [activeItem, setActiveItem] = useState(null);

  const mouseEnterHandler = (content, activeItem) => {
    // setIsDropdownVisible(true);
    setIsItemHovered(true);
    setDropdownContent(content);
    setActiveItem(activeItem);
  };

  const mouseLeaveHandler = () => {
    // setIsDropdownVisible(false);
    setIsItemHovered(false);
  };

  useEffect(() => {
    let timeout;

    if (isItemHovered) {
      setIsDropdownVisible(true);
    } else {
      timeout = setTimeout(() => {
        setIsDropdownVisible(false);
      }, 1500);
    }

    return () => {
      if (isItemHovered && timeout) clearTimeout(timeout);
    };
  }, [isItemHovered]);

  const listItems = listItemsData.map((item, i) => {
    return (
      <NavListItem
        key={i}
        index={i}
        item={item}
        onMouseEnter={mouseEnterHandler}
        onMouseLeave={mouseLeaveHandler}
      />
    );
  });

  return (
    <nav className={classes.Nav}>
      <ul className={classes.NavList}>{listItems}</ul>
      <NavListDropdown
        isVisible={isDropdownVisible}
        content={dropdownContent}
        activeItem={activeItem}
      />
      <div className={classes.NavLogo}>Logo</div>
      <div className={classes.NavCart}>
        <NavCart />
      </div>
    </nav>
  );
};

export default Nav;

问题分析

原代码中,useEffect的清理函数条件错误:当isItemHovered从false切换到true时,对应false状态的effect会执行清理函数,但闭包捕获的isItemHovered值仍是false,导致if (isItemHovered && timeout)条件不成立,无法清除之前设置的隐藏超时。当超时触发时,会错误地将下拉菜单设为隐藏,即使此时用户已经hover了其他菜单项。

修复方案

使用useRef保存超时ID,确保所有状态下都能访问到最新的超时引用;同时调整清理逻辑,无论当前isItemHovered状态如何,只要存在未执行的超时就清除。

修改后的代码

import { useState, useEffect, useRef } from 'react';

const Nav = (props) => {
  const [isItemHovered, setIsItemHovered] = useState(false);
  const [isDropdownVisible, setIsDropdownVisible] = useState(false);
  const [dropdownContent, setDropdownContent] = useState(null);
  const [activeItem, setActiveItem] = useState(null);
  // 用ref保存超时ID,确保跨effect访问
  const timeoutRef = useRef(null);

  const mouseEnterHandler = (content, activeItem) => {
    setIsItemHovered(true);
    setDropdownContent(content);
    setActiveItem(activeItem);
  };

  const mouseLeaveHandler = () => {
    setIsItemHovered(false);
  };

  useEffect(() => {
    // 先清除之前可能存在的超时
    if (timeoutRef.current) {
      clearTimeout(timeoutRef.current);
    }

    if (isItemHovered) {
      setIsDropdownVisible(true);
    } else {
      timeoutRef.current = setTimeout(() => {
        setIsDropdownVisible(false);
        // 超时执行后清空ref
        timeoutRef.current = null;
      }, 1500);
    }

    // 组件卸载或依赖变化时清除超时
    return () => {
      if (timeoutRef.current) {
        clearTimeout(timeoutRef.current);
      }
    };
  }, [isItemHovered]);

  const listItems = listItemsData.map((item, i) => {
    return (
      <NavListItem
        key={i}
        index={i}
        item={item}
        onMouseEnter={mouseEnterHandler}
        onMouseLeave={mouseLeaveHandler}
      />
    );
  });

  return (
    <nav className={classes.Nav}>
      <ul className={classes.NavList}>{listItems}</ul>
      <NavListDropdown
        isVisible={isDropdownVisible}
        content={dropdownContent}
        activeItem={activeItem}
      />
      <div className={classes.NavLogo}>Logo</div>
      <div className={classes.NavCart}>
        <NavCart />
      </div>
    </nav>
  );
};

export default Nav;

关键改动说明

  • 添加timeoutRef保存超时ID,避免闭包导致的旧值问题
  • 每次useEffect执行时先清除之前的超时,确保不会有遗留的超时任务
  • 清理函数无条件清除超时,保证组件卸载或状态切换时不会有残留的异步操作

内容的提问来源于stack exchange,提问作者Tidris

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.25 17:45:32