📌 前言
在移動端開發中,用戶對動畫的流暢性和過渡自然性有著極高的期待。最近我對一個使用 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 (<ModalisVisible={visible}animationIn={animationIn}animationOut={animationOut}backdropTransitionOutTiming={0}backdropTransitionInTiming={0}animationInTiming={300}animationOutTiming={300}useNativeDriveronBackdropPress={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 && (<Buttontitle={counting ? `${okText}(${countNumber}s)` : okText}blockonPress={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
的依賴管理,避免邏輯混亂