如何让react-native-progress环形进度条每30秒自动重启循环运行
环形进度条30秒自动循环实现方案
问题分析
你现有代码存在3个核心问题导致无法实现循环效果:
- 自定义Hook
useProgress嵌套定义在业务组件内部,不符合React开发规范,容易触发异常渲染 - 定时器存在闭包陷阱,读取的progress一直是初始值,逻辑判断不准确
- 缺少到达最大时间后的重置逻辑,进度跑完后就停止运行
修复后的完整实现
修正后的useProgress自定义Hook
// 定义在组件外部,符合Hook编写规范 const useProgress = (maxTimeInSeconds = 30) => { const [elapsedTime, setElapsedTime] = useState(0); useEffect(() => { const intervalId = setInterval(() => { setElapsedTime(prevTime => { // 到达最大时间直接重置为0,实现自动循环 if (prevTime >= maxTimeInSeconds) return 0; return prevTime + 1; }); }, 1000); return () => clearInterval(intervalId); }, [maxTimeInSeconds]); // 直接计算进度,无需额外维护state减少冗余更新 return elapsedTime / maxTimeInSeconds; };
完整业务代码
import { Observer } from "mobx-react"; import React, { useEffect, useState } from "react"; import { StyleSheet, Text, View, TouchableOpacity, Image, Platform, Dimensions, Clipboard, } from "react-native"; import * as Progress from "react-native-progress"; import AppStore from "../../stores/AppStore"; import Iconpack from "../../utils/Iconpack"; import Theme from "../../utils/Theme"; import DeviceInfo from "react-native-device-info"; import { Shadow } from "react-native-shadow-2"; import Toast from "react-native-simple-toast"; import FastImage from "react-native-fast-image"; const hRem = AppStore.screenHeight / 812; const wRem = AppStore.screenWidth / 375; // 自定义Hook放在组件外部 const useProgress = (maxTimeInSeconds = 30) => { const [elapsedTime, setElapsedTime] = useState(0); useEffect(() => { const intervalId = setInterval(() => { setElapsedTime(prevTime => { if (prevTime >= maxTimeInSeconds) return 0; return prevTime + 1; }); }, 1000); return () => clearInterval(intervalId); }, [maxTimeInSeconds]); return elapsedTime / maxTimeInSeconds; }; const OtpDetails = ({ route, navigation }) => { const { id, title } = route.params; const progress = useProgress(); const setTextIntoClipboard = async () => { await Clipboard.setString(title); Toast.show("OTP copied successfully!", 200); }; return ( <Observer> {() => ( <View style={{ flex: 1 }}> <FastImage style={styles.container} source={Iconpack.GLOBAL_BG} resizeMode={FastImage.resizeMode.cover} /> <View style={styles.headerStyle}> <TouchableOpacity onPress={() => { navigation.navigate("OtpScreen"); }} > <Image style={styles.backButton} source={Iconpack.GLOBAL_BACK_BUTTON} /> </TouchableOpacity> <Text style={styles.headerTitle}>{title}</Text> <View style={{ flexDirection: "row" }}> <TouchableOpacity> <Image style={[styles.editTrashButton, { marginRight: 19 }]} source={Iconpack.EDIT_ICON} /> </TouchableOpacity> <TouchableOpacity> <Image style={styles.editTrashButton} source={Iconpack.DELETE_ICON} /> </TouchableOpacity> </View> </View> <View style={styles.circleOuter}> {Platform.OS === "ios" ? ( <View style={styles.circleContainer}> <Progress.Circle style={{ alignItems: "center" }} progress={progress} size={Dimensions.get("window").width * 0.561} thickness={10} borderWidth={0} endAngle={10} strokeCap="round" color={"#354659"} unfilledColor={"#031830"} /> </View> ) : ( <Shadow distance={20} startColor={"rgba(27, 100, 206, 0.5)"} radius={210} safeRender={true} > <View style={styles.circleContainer}> <Progress.Circle style={{ alignItems: "center", overflow: "hidden" }} progress={progress} size={Dimensions.get("window").width * 0.561} thickness={10} borderWidth={0} endAngle={10} strokeCap="round" color={"#354659"} indeterminateAnimationDuration={2000} unfilledColor={"#031830"} /> </View> </Shadow> )} <Text style={[styles.circleItem]}>{title}</Text> <TouchableOpacity onPress={setTextIntoClipboard} style={styles.copyItem} > <Text style={styles.copyText}>TAP TO COPY</Text> </TouchableOpacity> </View> </View> )} </Observer> ); }; export default OtpDetails; const styles = StyleSheet.create({ container: { flex: 1, position: "absolute", top: 0, right: 0, left: 0, bottom: 0, backgroundColor: "#021831", }, headerStyle: { flexDirection: "row", marginTop: Platform.OS === "ios" ? 56 : 40, marginHorizontal: wRem * 26.66, justifyContent: "space-between", alignItems: "center", }, backButton: { width: wRem * 12, height: hRem * 21, }, editTrashButton: { width: wRem * 23, height: hRem * 23, }, headerTitle: { ...Theme.encodeSansMed2, color: "#FFFFFF", fontWeight: "600", marginLeft: wRem * 50, }, circleOuter: { flex: 1, justifyContent: "center", alignItems: "center", }, circleContainer: { borderRadius: Math.round( Dimensions.get("window").width + Dimensions.get("window").height ) / 2, width: Dimensions.get("window").width * 0.56, height: Dimensions.get("window").width * 0.56, justifyContent: "center", backgroundColor: "black", shadowColor: "rgba(27, 100, 206, 0.5)", shadowOffset: { width: 0, height: 0, }, shadowOpacity: 30, shadowRadius: 30, }, circleItem: { color: "white", ...Theme.encodeSansMed7, textAlign: "center", position: "absolute", width: "100%", paddingBottom: 100, }, copyItem: { justifyContent: "center", alignItems: "center", marginTop: hRem * 64, }, copyText: { color: "#3D4756", ...Theme.encodeSansMed4, }, });
核心改动说明
- 简化状态逻辑:移除冗余的progress状态,直接通过已流逝时间计算进度值,减少不必要的状态更新
- 修复闭包问题:使用状态更新回调函数获取上一次的时间值,避免定时器读取旧状态
- 新增循环逻辑:当已流逝时间等于最大时间时直接重置为0,自动开启下一轮进度加载
- 调整Hook定义位置:将自定义Hook从业务组件内部移到外部,符合React Hook编写规范
内容的提问来源于stack exchange,提问作者sourabh atigre
相关产品推荐
相关产品推荐

