React函数式组件移除前如何触发动画?
在React函数式组件移除前触发退出动画的实现方案
核心思路是通过状态控制组件的实际渲染时机,让组件在收到卸载信号后先执行退出动画,完成后再真正卸载,无需在父组件中硬编码动画逻辑。
实现步骤与代码示例
1. 封装带动画的子组件
子组件内部管理显示状态,监听父组件传递的渲染信号,自动触发入场/退场动画:
import React, { useEffect, useRef, useState } from "react"; // 假设你的npm动画包提供animate函数,支持传入DOM元素、动画类型和完成回调 import { animate } from "your-animation-package"; export default function AnimatedComponent({ shouldRender, onExitComplete }) { // 控制组件是否实际渲染的内部状态 const [isVisible, setIsVisible] = useState(shouldRender); // 保存组件DOM节点,用于动画操作 const componentRef = useRef(null); // 监听父组件的渲染状态变化 useEffect(() => { if (shouldRender) { // 触发入场动画 setIsVisible(true); animateStart(); } else { // 触发退场动画 animateExit(); } }, [shouldRender]); // 入场动画逻辑 const animateStart = () => { animate(componentRef.current, "enter", () => { console.log("入场动画完成"); }); }; // 退场动画逻辑:完成后更新状态,让组件卸载 const animateExit = () => { animate(componentRef.current, "exit", () => { console.log("退场动画完成"); setIsVisible(false); // 可选:通知父组件动画已完成 onExitComplete?.(); }); }; // 状态为false时,组件不再渲染,完成卸载 if (!isVisible) return null; return <div ref={componentRef} className="component" />; }
2. 父组件使用方式
父组件只需通过状态控制组件的显示/隐藏,无需关心动画细节:
import React, { useState } from "react"; import AnimatedComponent from "./AnimatedComponent"; export default function ParentComponent() { const [showComponent, setShowComponent] = useState(true); const handleRemoveComponent = () => { // 仅发送卸载信号,动画由子组件自行处理 setShowComponent(false); }; return ( <div> <button onClick={handleRemoveComponent}>移除组件</button> <AnimatedComponent shouldRender={showComponent} onExitComplete={() => console.log("组件已完全卸载")} /> </div> ); }
进阶:复用动画逻辑的自定义Hook
如果多个组件需要类似动画,可以封装自定义Hook提取通用逻辑:
import { useState, useEffect, useRef } from "react"; export function useAnimatedUnmount(shouldRender, animateEnter, animateExit) { const [isVisible, setIsVisible] = useState(shouldRender); const elementRef = useRef(null); useEffect(() => { if (shouldRender) { setIsVisible(true); animateEnter(elementRef.current); } else { // 支持Promise类型的动画函数 const finishExit = async () => { await animateExit(elementRef.current); setIsVisible(false); }; finishExit(); } }, [shouldRender, animateEnter, animateExit]); return { isVisible, elementRef }; }
子组件使用Hook简化代码:
import React from "react"; import { useAnimatedUnmount } from "./useAnimatedUnmount"; import { animate } from "your-animation-package"; export default function AnimatedComponent({ shouldRender, onExitComplete }) { const animateStart = (el) => animate(el, "enter"); const animateExit = (el) => animate(el, "exit"); const { isVisible, elementRef } = useAnimatedUnmount( shouldRender, animateStart, animateExit ); // 动画完成后通知父组件 useEffect(() => { if (!isVisible) onExitComplete?.(); }, [isVisible, onExitComplete]); if (!isVisible) return null; return <div ref={elementRef} className="component" />; }
关键注意事项
- 必须通过
useRef获取真实DOM节点,确保动画库能正确操作元素 - 依赖动画库提供的完成回调/Promise来触发组件卸载,避免用定时器模拟(易出现时序问题)
- 内部状态
isVisible是控制组件卸载的核心,父组件的shouldRender仅作为触发信号
内容的提问来源于stack exchange,提问作者ErikKey
相关产品推荐
相关产品推荐

