使用Hooks实现React组件退出动画(替代componentWillUnmount)
解决组件卸载前淡出动画不生效的问题
这个问题我之前踩过坑——useEffect的清理函数确实是在组件即将被卸载的最后一刻才执行,这时候DOM元素已经进入了移除流程,浏览器根本来不及渲染你的淡出动画。核心思路是不要让组件直接卸载,而是拆分“触发动画”和“实际卸载”两个步骤,具体实现方案如下:
核心方案:用状态控制动画与卸载时机
我们需要拆分两个阶段的状态逻辑:
- 标记组件“准备卸载”,触发淡出动画
- 监听动画结束事件,完成后真正移除组件
完整代码示例
组件实现(函数式)
import { useState, useRef, useEffect } from 'react'; function FadingCard({ isVisible, onAnimationComplete }) { const [isFadingOut, setIsFadingOut] = useState(false); const cardRef = useRef(null); // 监听父组件的显示/隐藏指令 useEffect(() => { if (!isVisible && !isFadingOut) { // 第一步:触发淡出动画 setIsFadingOut(true); } }, [isVisible]); // 监听动画结束,完成后通知父组件可以卸载自己 useEffect(() => { const handleAnimationEnd = () => { // 通知父组件更新状态,移除当前组件 onAnimationComplete(); // 重置淡出状态(可选,方便后续重新显示) setIsFadingOut(false); }; const element = cardRef.current; if (isFadingOut && element) { element.addEventListener('transitionend', handleAnimationEnd); } // 清理监听,避免内存泄漏 return () => { if (element) { element.removeEventListener('transitionend', handleAnimationEnd); } }; }, [isFadingOut, onAnimationComplete]); // 如果既不需要显示,也完成了淡出,直接不渲染 if (!isVisible && !isFadingOut) { return null; } return ( <div ref={cardRef} className={`fading-card ${isFadingOut ? 'fade-out' : ''}`} > 我会优雅地淡出消失 🎇 </div> ); } // 父组件使用示例 function ParentComponent() { const [showCard, setShowCard] = useState(true); const handleCardFadeComplete = () => { setShowCard(false); }; return ( <div> <button onClick={() => setShowCard(false)}>移除卡片</button> {showCard && ( <FadingCard isVisible={showCard} onAnimationComplete={handleCardFadeComplete} /> )} </div> ); }
对应的CSS样式
.fading-card { padding: 20px; background: #fff; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); /* 定义过渡效果 */ opacity: 1; transition: opacity 0.6s ease-out, transform 0.6s ease-out; transform: translateY(0); } .fade-out { opacity: 0; transform: translateY(20px); }
关键注意点
- 不要依赖useEffect清理函数做动画:清理函数执行时机太晚,DOM已经处于卸载队列中,动画无法被渲染。
- 必须监听动画/过渡结束事件:不能用
setTimeout硬编码延迟,因为不同浏览器的动画执行时长可能有差异,监听事件是最可靠的方式。 - 清理事件监听:一定要在useEffect的返回函数中移除监听,避免内存泄漏。
- 路由场景适配:如果是路由切换导致的组件卸载,可以结合
react-router的useLocation,在路由变化时触发淡出动画,等动画结束后再完成路由跳转(或者用react-transition-group库简化实现)。
内容的提问来源于stack exchange,提问作者Hoetmaaiers
相关产品推荐
相关产品推荐

