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

Mobx/React异步动画:如何等待组件完成动画后调用EE的next方法?

这是一个很典型的协调异步动作序列的场景,用Mobx的响应式能力可以很优雅地解决核心问题:跟踪所有组件的动画完成状态,待全部完成后再触发下一个事件。下面是具体的实现方案:

核心思路

我们需要在Store中维护两个关键状态:

  • 当前正在处理的事件(currentEvent)
  • 待完成的动画计数器(pendingAnimations)

当事件发射器(EE)触发新事件时,Store更新currentEvent并重置计数器;每个组件响应事件启动动画前,先通知Store“我要开始动画了”(增加计数器);动画完成后,再通知Store“我搞定了”(减少计数器)。当计数器归0时,就说明所有组件的动画都完成了,此时Store再调用EE的next方法触发下一个事件。

Store实现

首先创建一个Mobx Store来管理事件和动画状态:

import { makeAutoObservable } from "mobx";

class EventAnimationStore {
  // 当前正在处理的事件
  currentEvent = null;
  // 待完成的动画数量
  pendingAnimations = 0;
  // 事件发射器实例
  eventEmitter = null;

  constructor(eventEmitter) {
    makeAutoObservable(this);
    this.eventEmitter = eventEmitter;
    // 监听事件发射器的事件
    this.eventEmitter.on("event", (eventName) => {
      this._handleNewEvent(eventName);
    });
  }

  // 处理新到来的事件
  _handleNewEvent(eventName) {
    this.currentEvent = eventName;
    // 重置计数器,准备跟踪新一批动画
    this.pendingAnimations = 0;
  }

  // 组件注册动画开始
  registerAnimation() {
    this.pendingAnimations += 1;
  }

  // 组件通知动画完成
  completeAnimation() {
    this.pendingAnimations -= 1;
    this._checkIfAllDone();
  }

  // 组件卸载时取消未完成的动画
  cancelAnimation() {
    if (this.pendingAnimations > 0) {
      this.pendingAnimations -= 1;
      this._checkIfAllDone();
    }
  }

  // 检查是否所有动画都完成
  _checkIfAllDone() {
    if (this.pendingAnimations === 0 && this.currentEvent !== null) {
      // 所有动画完成,触发下一个事件
      this.eventEmitter.next();
      // 清空当前事件(可选,根据业务需求调整)
      this.currentEvent = null;
    }
  }
}

// 初始化Store,传入你的事件发射器实例
const eventAnimationStore = new EventAnimationStore(yourEventEmitterInstance);

React组件实现

用mobx-react-lite的observer包裹组件,让组件响应Store的currentEvent变化,执行异步动画并正确通知Store状态:

import { observer } from "mobx-react-lite";
import { useEffect } from "react";

const AnimatedCard = observer(({ store }) => {
  useEffect(() => {
    let animationFrameId = null;
    let isUnmounted = false;

    const runAsyncAnimation = async () => {
      if (!store.currentEvent) return;

      // 告诉Store:我要开始动画了
      store.registerAnimation();

      try {
        // 模拟异步动画(替换成你实际的动画逻辑,比如GSAP、CSS动画等)
        await new Promise(resolve => {
          animationFrameId = requestAnimationFrame(() => {
            console.log(`Executing animation for event: ${store.currentEvent}`);
            // 这里写你的动画代码
            resolve();
          });
        });

        if (!isUnmounted) {
          // 动画完成,告诉Store
          store.completeAnimation();
        }
      } catch (err) {
        console.error("Animation failed:", err);
        // 即使动画失败,也要通知Store,避免流程卡住
        if (!isUnmounted) {
          store.completeAnimation();
        }
      }
    };

    runAsyncAnimation();

    // 组件卸载时的清理逻辑
    return () => {
      isUnmounted = true;
      if (animationFrameId) {
        cancelAnimationFrame(animationFrameId);
      }
      // 通知Store取消这个未完成的动画
      store.cancelAnimation();
    };
  }, [store.currentEvent]);

  return <div className="animated-card">Content to animate</div>;
});

关键细节说明

  1. 异常处理:在动画的catch块中也要调用completeAnimation,避免因为某个动画失败导致整个流程卡住。
  2. 组件卸载处理:在useEffect的清理函数中调用cancelAnimation,防止组件卸载后计数器一直处于非0状态,导致后续事件无法触发。
  3. 响应式触发:因为pendingAnimations是Mobx observable,所有对它的修改都会自动触发Store内部的状态检查,无需手动监听。

内容的提问来源于stack exchange,提问作者Giggioz

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.25 06:19:12