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

使用react-native-redash关闭模态框时出现意外闪烁问题求助

解决React Native Reanimated模态框关闭时的闪烁问题

嘿,我来帮你搞定这个关闭时闪烁的问题~ 看了你的代码,主要是几个状态同步和动画控制的小问题,咱们一步步来修复:

问题根源分析

  1. 动画值未持久化:你直接用new Value(0)和new Clock(),组件每次渲染都会创建新的实例,导致动画状态在重新渲染时丢失,这是闪烁的核心原因。
  2. 状态同步不一致:维护了本地的visible和message state,又通过useEffect和props同步,容易出现状态冲突,导致动画和组件渲染不同步。
  3. useCode依赖缺失:依赖数组只加了animation,当visible变化时,动画的起始/目标值不会更新,导致动画逻辑出错。
  4. 关闭回调时机不准确:用setTimeout来延迟调用onClosed,时间和动画时长不匹配,可能导致组件在动画未完成时就触发后续操作。

修复步骤

1. 使用Reanimated的hooks持久化动画值

把new Value和new Clock换成Reanimated提供的useValue和useClock,确保动画状态在组件渲染过程中保持稳定:

import { useValue, useClock } from 'react-native-reanimated';

// 替换原来的new Value和new Clock
const animation = useValue(0);
const clock = useClock();

2. 移除本地状态,直接使用props(状态单一来源)

不需要维护本地的visible和message,直接用props传递的值,避免同步问题:

// 移除这两行本地state
// const [visible, setVisible] = React.useState(false);
// const [message, setMessage] = React.useState(null);

// 直接解构props
const { visible, message, onClosed, onClose } = props;

3. 修正useCode的逻辑和依赖

更新useCode的依赖数组,加入visible,并优化动画触发逻辑,同时在动画完成后自动调用onClosed,不需要手动加setTimeout:

useCode(() => block([
  // 当时钟未运行时,初始化动画的起始值
  cond(not(clockRunning(clock)), [
    set(animation, visible ? 0 : 1),
  ]),
  // 执行动画
  set(
    animation,
    timing({
      clock,
      from: animation, // 用当前动画值作为起始,避免硬编码导致的跳变
      to: visible ? 1 : 0,
      duration: 500,
      easing: Easing.inOut(Easing.ease)
    })
  ),
  // 当动画完成且模态框要关闭时,调用关闭回调
  cond(and(not(visible), not(clockRunning(clock))), call([], () => {
    onClosed?.();
  }))
]), [visible]);

4. 调整关闭按钮的逻辑

关闭按钮只需要通知父组件更新visible状态,由父组件控制模态框的显示/隐藏:

<TouchableOpacity 
  activeOpacity={1} 
  onPress={() => {
    // 通知父组件关闭,由父组件把visible设为false
    onClose?.();
  }} 
  style={styles.close}>
  <Image source={require('app/src/assets/images/common/close_x2.png')} style={{ width: 16, height: 16, resizeMode: 'contain' }} />
</TouchableOpacity>

完整修改后的代码

import React from 'react';
import { TouchableOpacity, Text, Image, StyleSheet } from 'react-native';
import { Animated, Easing } from 'react-native-reanimated';
import { useValue, useClock, useCode, block, cond, not, clockRunning, set, timing, call } from 'react-native-reanimated';

const Alert = (props) => {
  const { visible, message, onClosed, onClose } = props;
  const animation = useValue(0);
  const clock = useClock();

  useCode(() => block([
    cond(not(clockRunning(clock)), [
      set(animation, visible ? 0 : 1),
    ]),
    set(
      animation,
      timing({
        clock,
        from: animation,
        to: visible ? 1 : 0,
        duration: 500,
        easing: Easing.inOut(Easing.ease)
      })
    ),
    cond(and(not(visible), not(clockRunning(clock))), call([], () => {
      onClosed?.();
    }))
  ]), [visible]);

  const scale = animation; // mix(animation,0,1)等价于animation本身
  const opacity = animation;
  const from = 'transparent';
  const to = 'rgba(0,0,0,.75)';
  const backgroundColor = Animated.interpolateColor(animation, {
    inputRange: [0, 1],
    outputRange: [from, to]
  });

  // 当visible为false且动画完成时,返回null(可选,避免组件留在DOM中)
  if (!visible && animation._value === 0) {
    return null;
  }

  return (
    <Animated.View style={[styles.container, { backgroundColor, transform: [{ scale }] }]}>
      <Animated.View style={[
        styles.inner,
        { transform: [{ scale }], opacity }
      ]}>
        <TouchableOpacity 
          activeOpacity={1} 
          onPress={() => onClose?.()} 
          style={styles.close}>
          <Image source={require('app/src/assets/images/common/close_x2.png')} style={{ width: 16, height: 16, resizeMode: 'contain' }} />
        </TouchableOpacity>
        <Text style={styles.message}>{ message }</Text>
      </Animated.View>
    </Animated.View>
  )
}

// 记得补充你的styles
const styles = StyleSheet.create({
  container: {
    position: 'absolute',
    top: 0,
    left: 0,
    right: 0,
    bottom: 0,
    justifyContent: 'center',
    alignItems: 'center',
  },
  inner: {
    backgroundColor: 'white',
    padding: 20,
    borderRadius: 8,
    width: '80%',
    position: 'relative',
  },
  close: {
    position: 'absolute',
    top: 10,
    right: 10,
  },
  message: {
    fontSize: 16,
    textAlign: 'center',
  }
});

export default Alert;

额外说明

  • 父组件需要维护visible状态,当点击关闭按钮时,父组件把visible设为false,触发动画。
  • 动画完成后会自动调用onClosed,不需要手动加延迟,确保时机准确。
  • 最后加了一个判断,当模态框完全隐藏后返回null,避免组件留在DOM中占用资源。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.07 21:37:57