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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.18 02:55:20