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
相关产品推荐
相关产品推荐

