📌 前言
在移动端开发中,用户对动画的流畅性和过渡自然性 有着极高的期待。最近我对一个使用 react-native-modal
实现的 Alert
弹窗组件进行了优化,成功解决了闪烁和卡顿问题,并显著提升了用户体验。
本篇博客将带你深入了解优化的全过程,并提供完整可复用的解决方案。
🎯 问题描述
原始弹窗组件逻辑如下:
-
使用
bounceIn
/bounceOut
实现进出场动画 -
动画过程中出现闪烁现象
-
使用状态控制不够精准,组件经常在动画未完成时就被卸载
-
倒计时逻辑不够鲁棒,容易导致重复调用
🛠️ 解决方案
1. 替换动画效果
将动画更换为更平滑的 fadeIn
和 fadeOut
,避免抖动与视觉突兀感:
animationIn="fadeIn"
animationOut="fadeOut"
animationInTiming={300}
animationOutTiming={300}
backdropTransitionInTiming={0}
backdropTransitionOutTiming={0}
2. 状态控制组件显示
引入 isVisible
状态,在动画完成后再卸载组件,确保动画完整播放:
useEffect(() => {
if (visible) {
setIsVisible(true);
} else {
const timer = setTimeout(() => {
setIsVisible(false);
}, 300); // 动画时长
return () => clearTimeout(timer);
}
}, [visible]);
3. 使用 useNativeDriver 提升动画性能
开启原生线程驱动动画,大幅度减少 JavaScript 主线程阻塞带来的卡顿问题:
useNativeDriver
4. 自动跳转逻辑优化
精简并修复倒计时逻辑,避免 setInterval 注册冲突:
useEffect(() => {
setCounting(autoSend);
}, [autoSend]);
useEffect(() => {
if (autoSend) {
timer.current = setInterval(() => {
if (countNumber <= 1) {
onOk();
setCounting(false);
return;
}
setCountNumber(prev => prev - 1);
}, 1000);
return () => clearInterval(timer.current);
}
}, [countNumber]);
🧩 完整优化代码
import React, { useRef, useState, useEffect } from 'react';
import { Text, View } from 'react-native';
import Modal from 'react-native-modal';
import Button from '../Button';
import { Props } from './types';
import styles from './styles';
const Alert: React.FC<Props> = ({
animationIn = 'fadeIn',
animationOut = 'fadeOut',
visible = false,
onOk = () => {},
onCancel,
cancelText = '取消',
okText = '立即前往',
customStyles,
children,
title,
subtitle,
subtitleTwo,
autoSend,
onBackdropPress = true,
}) => {
const [countNumber, setCountNumber] = useState(3);
const [counting, setCounting] = useState(false);
const timer: any = useRef(null);
const [isVisible, setIsVisible] = useState(false);
useEffect(() => {
if (visible) {
setIsVisible(true);
} else {
const timer = setTimeout(() => {
setIsVisible(false);
}, 300);
return () => clearTimeout(timer);
}
}, [visible]);
useEffect(() => {
setCounting(autoSend);
}, [autoSend]);
useEffect(() => {
if (autoSend) {
timer.current = setInterval(() => {
if (countNumber <= 1) {
onOk();
setCounting(false);
return;
}
setCountNumber(prev => prev - 1);
}, 1000);
return () => clearInterval(timer.current);
}
}, [countNumber]);
if (!isVisible && !visible) {
return null;
}
return (
<Modal
isVisible={visible}
animationIn={animationIn}
animationOut={animationOut}
backdropTransitionOutTiming={0}
backdropTransitionInTiming={0}
animationInTiming={300}
animationOutTiming={300}
useNativeDriver
onBackdropPress={onBackdropPress ? onCancel : () => {}}
>
<View style={[styles.container, customStyles?.container]}>
{typeof title === 'string' ? <Text style={styles.title}>{title}</Text> : title}
{typeof subtitle === 'string' ? <Text style={styles.subtitle}>{subtitle}</Text> : subtitle}
{typeof subtitleTwo === 'string' ? (
<Text style={[styles.subtitleTwo, customStyles?.subtitleTwo]}>{subtitleTwo}</Text>
) : (
subtitleTwo
)}
{children}
<View style={[styles.button, customStyles?.button]}>
{okText && (
<Button
title={counting ? `${okText}(${countNumber}s)` : okText}
block
onPress={onOk}
/>
)}
{cancelText && (
<Button title={cancelText} block type="text" onPress={onCancel} />
)}
</View>
</View>
</Modal>
);
};
export default Alert;
✅ 优化效果总结
项目 | 优化前 | 优化后 |
---|---|---|
动画流畅度 | 有抖动、闪烁 | 平滑、自然 |
动画卸载 | 过早卸载 | 动画结束后卸载 |
组件性能 | JS 主线程处理 | 原生驱动 |
倒计时准确性 | 有异常 | 精准触发 |
📎 技术建议
-
弹窗组件最好 延迟卸载,否则动画会被打断
-
不建议使用过于夸张的动画效果,如
bounceIn
、zoomIn
,在 UX 层面可能不友好 -
useNativeDriver
是优化 React Native 动画的必备武器 -
注意
setInterval
和useEffect
的依赖管理,避免逻辑混乱