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>; });
关键细节说明
- 异常处理:在动画的
catch块中也要调用completeAnimation,避免因为某个动画失败导致整个流程卡住。 - 组件卸载处理:在
useEffect的清理函数中调用cancelAnimation,防止组件卸载后计数器一直处于非0状态,导致后续事件无法触发。 - 响应式触发:因为
pendingAnimations是Mobx observable,所有对它的修改都会自动触发Store内部的状态检查,无需手动监听。
内容的提问来源于stack exchange,提问作者Giggioz
相关产品推荐
相关产品推荐

