使用react-native-redash关闭模态框时出现意外闪烁问题求助
解决React Native Reanimated模态框关闭时的闪烁问题
嘿,我来帮你搞定这个关闭时闪烁的问题~ 看了你的代码,主要是几个状态同步和动画控制的小问题,咱们一步步来修复:
问题根源分析
- 动画值未持久化:你直接用
new Value(0)和new Clock(),组件每次渲染都会创建新的实例,导致动画状态在重新渲染时丢失,这是闪烁的核心原因。 - 状态同步不一致:维护了本地的
visible和messagestate,又通过useEffect和props同步,容易出现状态冲突,导致动画和组件渲染不同步。 useCode依赖缺失:依赖数组只加了animation,当visible变化时,动画的起始/目标值不会更新,导致动画逻辑出错。- 关闭回调时机不准确:用
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
相关产品推荐
相关产品推荐

