无法清除本机应用程序中的 setInterval

Unable to clear setInterval in react native app

我有这个非常简单的应用程序,我试图在其中实现倒计时,StartStop 功能似乎工作正常但是当我按下 Stop 时,值seconds 没有在视图中更新,这是期望的行为,但是当我观察控制台日志时,它显示 sec 的值不断变化,我猜这表明 setInterval 仍然是 运行 且未清除。

附上代码:

import React, { useState, useEffect } from "react";
import {
  StyleSheet,
  Text,
  View,
  StatusBar,
  TouchableOpacity,
  Dimensions,
} from "react-native";

const screen = Dimensions.get("screen");

export default function App() {
  const [seconds, setSeconds] = useState(4);
  const [start, setStartToggle] = useState(true);
  let [fun, setFun] = useState(null);
  const getRemaining = () => {
    const minute = Math.floor(seconds / 60);
    const second = seconds - minute * 60;
    return formatTime(minute) + ":" + formatTime(second);
  };

  const formatTime = (time) => {
    return ("0" + time).slice(-2);
  };

  const startTimer = () => {
    setStartToggle(false);

    console.log("StartTimer");
    let sec = seconds;
    setFun(
      setInterval(() => {
        console.log("akak:", sec);
        if (sec <= 0) stopTimer();
        setSeconds(sec--);
      }, 1000)
    );
  };

  const stopTimer = () => {
    setStartToggle(true);
    console.log("StopTimer");
    clearInterval(fun);
    setFun(null);
  };

  return (
    <View style={styles.container}>
      <StatusBar barStyle="light-content" />
      <Text style={styles.timerText}>{getRemaining(seconds)}</Text>
      {start ? (
        <TouchableOpacity onPress={startTimer} style={styles.button}>
          <Text style={styles.buttonText}>Start</Text>
        </TouchableOpacity>
      ) : (
        <TouchableOpacity
          onPress={stopTimer}
          style={[styles.button, { borderColor: "orange" }]}
        >
          <Text style={styles.buttonText}>Stop</Text>
        </TouchableOpacity>
      )}
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: "#07121B",
    alignItems: "center",
    justifyContent: "center",
  },
  button: {
    borderWidth: 10,
    borderColor: "#89aaff",
    width: screen.width / 2,
    height: screen.width / 2,
    borderRadius: screen.width / 2,
    justifyContent: "center",
    alignItems: "center",
  },
  buttonText: {
    fontSize: 40,
    color: "#89aaff",
    fontWeight: "bold",
  },

  timerText: {
    fontSize: 90,
    color: "#89aaff",
    fontWeight: "bold",
    marginBottom: 20,
  },
});

应用行为:

即使在点击停止后仍显示输出:

感谢 Emiel Zuurbierlissettdm 的回答,我的代码现在可以工作了。

工作代码:

// import { StatusBar } from "expo-status-bar";
import React, { useState, useEffect, useRef } from "react";
import {
  StyleSheet,
  Text,
  View,
  StatusBar,
  TouchableOpacity,
  Dimensions,
} from "react-native";

const screen = Dimensions.get("screen");

export default function App() {
  const [seconds, setSeconds] = useState(11);
  const funRef = useRef(null);
  const [start, setStartToggle] = useState(true);

  const getRemaining = () => {
    const minute = Math.floor(seconds / 60);
    const second = seconds - minute * 60;
    return formatTime(minute) + ":" + formatTime(second);
  };

  const formatTime = (time) => {
    return ("0" + time).slice(-2);
  };
  let sec = seconds;
  useEffect(() => {
    // let timer = null;

    if (!start) {
      let sec = seconds;
      funRef.current = setInterval(() => {
        console.log("Seconds remaining:", sec);
        if (sec <= 0) {
          clearInterval(funRef.current);
          setStartToggle(true);
        }
        setSeconds(sec--);
      }, 1000);
    } else {
      clearInterval(funRef.current);
    }
  }, [start]);

  const startTimer = () => {
    setSeconds(sec);
    setStartToggle(false);
  };

  const stopTimer = () => {
    setSeconds(sec);
    setStartToggle(true);
  };

  return (
    <View style={styles.container}>
      <StatusBar barStyle="light-content" />
      <Text
        style={
          seconds ? styles.timerText : [styles.timerText, { color: "red" }]
        }
      >
        {getRemaining(seconds)}
      </Text>
      {start ? (
        <TouchableOpacity onPress={startTimer} style={styles.button}>
          <Text style={styles.buttonText}>Start</Text>
        </TouchableOpacity>
      ) : (
        <TouchableOpacity
          onPress={stopTimer}
          style={[styles.button, { borderColor: "orange" }]}
        >
          <Text style={styles.buttonText}>Stop</Text>
        </TouchableOpacity>
      )}
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: "#07121B",
    alignItems: "center",
    justifyContent: "center",
  },
  button: {
    borderWidth: 10,
    borderColor: "#89aaff",
    width: screen.width / 2,
    height: screen.width / 2,
    borderRadius: screen.width / 2,
    justifyContent: "center",
    alignItems: "center",
  },
  buttonText: {
    fontSize: 40,
    color: "#89aaff",
    fontWeight: "bold",
  },

  timerText: {
    fontSize: 90,
    color: "#89aaff",
    fontWeight: "bold",
    marginBottom: 20,
  },
});

目前您使用 useState 挂钩来存储区间参考。在 App 组件的每个 re-render 处, fun 状态都会重置,但不会是对间隔的完全相同的引用。

而是使用 useRef 钩子。它可以创建对在 re-render 秒期间不会更改的间隔的引用。这意味着引用的 current 属性 中的值将始终完全相同。

最重要的是,使用 useEffect 钩子来观察 运行 状态何时设置或取消设置,并根据该状态启动和停止计时器。

import React, { useState, useRef, useEffect } from 'react'

export default function App() {
  const [isRunning, setIsRunning] = useState(false);
  const funRef = useRef(null);

  const startTimer = () => {
    if (!isRunning) {
      setIsRunning(true);
    }
  };

  const stopTimer = () {
    if (isRunning && funRef.current !== null) {
      setIsRunning(false);
    }
  };

  useEffect(() => {
    if (isRunning) {
      funRef.current = setInterval(() => { // Save reference to interval.
        // ...
      }, 1000);
    } else {
      clearInterval(funRef.current); // Stop the interval.
    }
  }, [isRunning]);

  // ...
}

在useEffect块中添加间隔定时器,运行此块每次开始变化:

  useEffect(()=> {
        let timer = null;
    
        if(!start) {
          let sec = seconds;
          timer = setInterval(() => {
            console.log("akak:", sec);
            if (sec <= 0) clearInterval(timer);
            setSeconds(sec--);
          }, 1000);
        } else {
          clearInterval(timer);
       }
   }, [start]);


      const startTimer = () => {
        setStartToggle(false);
      };


      const stopTimer = () => {
        setStartToggle(true);
      };

此外,我建议您使用准确的剩余时间。 Javascript是单线程语言,这个间隔可以超过一秒

编辑:使用准确时间。

  useEffect(() => {
    let timer = null;

    if (!start) {
      let sec = seconds;
      const startTime = Date.now();
      timer = setInterval(() => {
        const currentTime = Date.now();
        sec = sec - Math.floor((currentTime - startTime) / 1000);
        if (sec < 0) {
          clearInterval(timer);
          setSeconds(0);
        } else {
          setSeconds(sec);
        }
      }, 1000);
    }
  }, [start]);