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

React条件渲染下实现组件平滑挂载/卸载动画的最优方案

在React中实现组件平滑挂载/卸载动画的正确方式

先纠正你给出的代码问题

你贴的示例里有两个明显的问题:

  • divRef定义后没绑定到目标div上,点击按钮时divRef.current会是null,直接调用setAttribute会报错
  • 直接操作DOM属性不符合React的状态驱动理念,应该通过状态控制组件的类名或样式,而非手动修改DOM

两种方案的可靠性对比

1. onAnimationEnd/onTransitionEnd 是更可靠的选择

这种方案是浏览器原生提供的动画结束监听,完全跟随动画实际执行的结束时机,不会受设备性能、浏览器卡顿等因素影响。只要判断好对应的动画/过渡名称,就能精准触发组件卸载。

你觉得它不可靠可能是因为示例代码的写法问题,而非方案本身的缺陷。正确使用时,它是最贴合浏览器行为的方案。

2. setTimeout方案的局限性

用setTimeout硬编码动画时长的问题很明显:

  • 必须和CSS里的动画时长严格同步,后续修改动画时长时,JS代码也要跟着改,维护成本高
  • 一旦浏览器卡顿或设备性能不足,动画实际结束时间会晚于setTimeout触发时间,导致组件提前卸载,出现视觉断裂

正确的原生React实现思路

核心逻辑是:用状态分阶段控制组件的显示和动画状态,先触发退出动画,等动画实际结束后再卸载组件;挂载时同理,先渲染组件再触发进入动画。

以下是一个完整的淡入淡出示例:

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

const AnimatedBox = () => {
  // 控制组件是否挂载
  const [isMounted, setIsMounted] = useState(true);
  // 控制是否处于退出动画阶段
  const [isExiting, setIsExiting] = useState(false);
  const boxRef = useRef(null);

  const handleClose = () => {
    // 触发退出动画
    setIsExiting(true);
  };

  const handleAnimationEnd = (e) => {
    // 只在退出动画结束后卸载组件
    if (e.animationName === 'fadeOut') {
      setIsMounted(false);
    }
  };

  useEffect(() => {
    // 组件挂载完成后触发进入动画
    if (isMounted && !isExiting) {
      boxRef.current.classList.add('fade-in');
    }
  }, [isMounted, isExiting]);

  return (
    <>
      {isMounted && (
        <div
          ref={boxRef}
          className={`box ${isExiting ? 'fade-out' : ''}`}
          onAnimationEnd={handleAnimationEnd}
        >
          我是带平滑动画的组件
        </div>
      )}
      <button onClick={handleClose} disabled={isExiting}>
        关闭组件
      </button>
    </>
  );
};

/* 对应的CSS样式 */
/*
.box {
  width: 200px;
  height: 200px;
  background: #4285F4;
  opacity: 0;
}

.fade-in {
  animation: fadeIn 0.4s ease-out forwards;
}

.fade-out {
  animation: fadeOut 0.4s ease-out forwards;
}

@keyframes fadeIn {
  from { opacity: 0; transform: translateY(20px); }
  to { opacity: 1; transform: translateY(0); }
}

@keyframes fadeOut {
  from { opacity: 1; transform: translateY(0); }
  to { opacity: 0; transform: translateY(20px); }
}
*/

额外实用建议

  • 如果项目中频繁需要处理动画,推荐使用成熟的动画库(如Framer Motion、React Transition Group),它们封装了状态管理、动画监听等细节,能大幅减少手动代码
  • 动画过程中建议禁用触发按钮(如示例中的disabled={isExiting}),防止用户重复操作导致状态混乱
  • 始终遵循React的状态驱动原则,避免直接操作DOM,所有样式变化都通过状态控制类名或内联样式实现

内容的提问来源于stack exchange,提问作者Tarek-Dev

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 16:22:15