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

如何让react-native-progress环形进度条每30秒自动重启循环运行

环形进度条30秒自动循环实现方案

问题分析

你现有代码存在3个核心问题导致无法实现循环效果:

  1. 自定义Hook useProgress 嵌套定义在业务组件内部,不符合React开发规范,容易触发异常渲染
  2. 定时器存在闭包陷阱,读取的progress一直是初始值,逻辑判断不准确
  3. 缺少到达最大时间后的重置逻辑,进度跑完后就停止运行

修复后的完整实现

修正后的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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.25 09:06:09