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

React移动端菜单指定span过渡结束后关闭功能异常求解

问题根源

你现有代码存在3个核心问题,才会导致二次打开自动关闭:

  • 直接在组件渲染阶段执行DOM查询、事件绑定,每次组件重渲染都会新增一个transitionend监听,且从未做事件解绑,残留的监听回调会在后续动画触发时意外执行
  • 没有区分「菜单打开阶段的过渡」和「菜单关闭阶段的过渡」,只要.end元素触发过渡结束就直接翻转isActive状态,打开菜单时的入场动画结束后也会触发状态翻转,直接把刚打开的菜单关掉
  • 在React组件中直接使用document.querySelectorAll操作DOM、手动绑定原生事件,违反React声明式编程逻辑,极易出现状态不同步、内存泄漏问题
实现方案

不需要使用useTransition,仅用useRef+useEffect管理DOM引用和事件监听,加一个标记位区分开/关阶段的动画即可,实现代码如下:

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

// 组件内逻辑
const [isActive, setIsActive] = useState(false);
// 绑定最后一个span的DOM实例
const lastSpanRef = useRef(null);
// 标记当前是否在等待关闭动画结束
const waitCloseAnim = useRef(false);

const toggleNavBar = () => {
  if (isActive) {
    // 触发关闭时先打标记,等动画结束再真正修改状态
    waitCloseAnim.current = true;
  } else {
    // 打开菜单直接切换状态,重置标记
    setIsActive(true);
    waitCloseAnim.current = false;
  }
};

useEffect(() => {
  const handleTransitionEnd = () => {
    // 仅在等待关闭的状态下,动画结束才执行关闭
    if (waitCloseAnim.current) {
      setIsActive(false);
      waitCloseAnim.current = false;
    }
  };
  const target = lastSpanRef.current;
  if (target) {
    target.addEventListener('transitionend', handleTransitionEnd);
  }
  // 组件卸载时移除监听,避免内存泄漏
  return () => {
    if (target) target.removeEventListener('transitionend', handleTransitionEnd);
  };
}, []);

对应的JSX部分只需要给最后一个span补充ref绑定即可,其他结构不变:

<nav className={isActive ? Style.navBarMobile : Style.navBar}>
  <ul className={Style.navBarList}>
    <li className={Style.navBarListItem}>
      <NavLink className={Style.navBarListLink} to='/a_propos'>
        <div className={Style.original}>
          <span>à</span>
          <span>&nbsp;</span>
          <span>p</span>
          <span>r</span>
          <span>o</span>
          <span>p</span>
          <span>o</span>
          <span>s</span>
        </div>
        <div className={Style.hoverClone}>
          <span>à</span>
          <span>&nbsp;</span>
          <span>p</span>
          <span>r</span>
          <span>o</span>
          <span>p</span>
          <span>o</span>
          <span ref={lastSpanRef} className='end'>s</span>
        </div>
      </NavLink>
    </li>
    {/* 菜单开关按钮直接绑定toggleNavBar即可 */}
  </ul>
</nav>
逻辑说明
  • 用useRef直接获取目标DOM节点,替代原生DOM查询方法,符合React的DOM管理规则
  • waitCloseAnim标记位只在用户主动触发关闭时置为true,打开菜单过程中触发的transitionend事件不会执行关闭逻辑
  • 事件监听仅在组件挂载时绑定一次,卸载时自动移除,不会出现重复绑定、回调意外触发的问题

内容的提问来源于stack exchange,提问作者Alex.f

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 09:39:19