React Native 弹框组件“失灵” ,一次动画并发问题的排查与修复

React Native 弹框组件“失灵” ,一次动画并发问题的排查与修复

在一个基于 Animated 实现的自定义弹框组件 AppActionSheet 中,业务页面触发弹框显示时,出现弹框“出不来”的情况 —— 调用方已将 isShow 设为 true,但 UI 上没有任何反应,且无报错。

首先检查 componentDidUpdate 中的判断逻辑

componentDidUpdate(prevProps) { if (this.props.isShow && !prevProps.isShow) { this.registerBackHandler(); this.show(); } else if (!this.props.isShow && prevProps.isShow) { this.hide(); } }

逻辑本身没有问题:isShow 从 false → true 调用 show(),反之调用 hide()。问题不在这个入口。

查看核心实现:

show = () => { this.setState({ visible: true }); Animated.timing(this.slideAnim, { toValue: 0, duration: 200, useNativeDriver: true, }).start(); }; hide = () => { Animated.timing(this.slideAnim, { toValue: screenHeight, duration: 200, useNativeDriver: true, }).start(() => { this.setState({ visible: false }); this.removeBackHandler(); }); };

hide() 启动了一个 200ms 的滑出动画,并在动画完成回调中将 visible 置为 false。如果在这 200ms 内再次调用 show(),show() 会执行 setState({ visible: true }),但旧的 hide 动画并未被取消。旧动画执行完毕后,其回调会将 visible 再次设为 false,覆盖掉刚刚设置的 true。

方案:在启动新动画前,停止所有正在运行的动画。引入一个标志变量,在动画回调中判断当前“期望”的可见状态,避免过期回调误操作。组件卸载时清理,防止内存泄漏。

最终实现:

export class AppActionSheet extends React.Component { constructor(props) { super(props); this.state = { visible: false }; this.slideAnim = new Animated.Value(screenHeight); this.animation = null; // 保存当前动画实例 this.isVisibleExpected = false; // 期望状态标志 } show = () => { this.isVisibleExpected = true; // 取消正在运行的动画 if (this.animation) { this.animation.stop(); this.animation = null; } this.setState({ visible: true }); this.animation = Animated.timing(this.slideAnim, { toValue: 0, duration: 200, useNativeDriver: true, }); this.animation.start(() => { this.animation = null; }); }; hide = () => { this.isVisibleExpected = false; if (this.animation) { this.animation.stop(); this.animation = null; } this.animation = Animated.timing(this.slideAnim, { toValue: screenHeight, duration: 200, useNativeDriver: true, }); this.animation.start(() => { // 只有期望为 false 时才真正隐藏 if (!this.isVisibleExpected) { this.setState({ visible: false }); this.removeBackHandler(); } this.animation = null; }); }; componentWillUnmount() { // 组件卸载时停止动画 if (this.animation) { this.animation.stop(); this.animation = null; } this.removeBackHandler(); } }

ok.