【问题标题】:Problem updating state when it is being used in an interval在间隔内使用时更新状态时出现问题
【发布时间】:2020-04-02 23:15:30
【问题描述】:

我正在构建一个 React Native 应用程序并尝试创建一个计时器。计时器在前台正常运行,使用 setInterval 并更新一些状态,该状态保存秒数并每隔一秒减少一次。当应用程序在后台运行时,会捕获并存储日期。当活动再次激活时,我试图计算两个日期之间的差异,然后更新状态以反映差异,以便用户看到计时器正在后台运行。

我遇到了一个问题,即计时器运行时状态未更新。

const home = () => {

  const WORK_SECONDS = 1500; //1500
  const SHORT_REST_SECONDS = 300; //300
  const LONG_REST_SECONDS = 900; //900
  const ACTIVE_COLOR = '#009688';
  const PAUSED_COLOR = '#00695C';
  const ADD_TIME = 60;
  const NOTIFICATION_TITLE = "Time's up!";
  const NOTIFICATION_BODY = "Finished!";

  const [timer, setTimer] = useState({seconds: WORK_SECONDS, timeCap: WORK_SECONDS });
  const [isActive, setIsActive] = useState(false);
  const [timerColor, setTimerColor] = useState(ACTIVE_COLOR);
  const [sessionCycle, setSessionCycle] = useState([]);
  let time = 0;

  const toggle = () => {
    if (sessionCycle.length == 8) setSessionCycle([]);
    setIsActive(!isActive);
    setTimerColor((timerColor == ACTIVE_COLOR && timer.seconds != timer.timeCap) ? PAUSED_COLOR : ACTIVE_COLOR);
  }

  const reset = () => {
    setTimer({...timer, seconds: timer.timeCap});
    setIsActive(false);
  }

    useEffect(() => {
        AppState.addEventListener('change', handleChange);  
        return () => {
          AppState.removeEventListener('change', handleChange);  
        }
      }, []);

      const handleChange = (newState) => {
        if (newState === "active") {
          let resumeTime = moment(new Date());
          let exitTime = moment(time);
          let duration = moment.duration(resumeTime.diff(exitTime, 'seconds'));
          let diff = duration.asSeconds() * 1000;
          console.log("Attempting to substract - " + diff);
          console.log(timer.seconds + " from appstate");
          setTimer({...timer, seconds: timer.seconds - diff})
          console.log(timer.seconds + " after subtraction");
        }
        else if (newState == "background") {
          time = new Date();
        }
      }

      useEffect(() => {
        let interval = null;
        if (isActive) {
          if (timer.seconds == 0) {
            //sendNotification(); 
            // TODO: Add +1 to pomo total after finishing work session
            updateSessionCycle("forward");  
            clearInterval(interval);
            toggle();
          }
          interval = setInterval(() => {
            console.log(timer.seconds);
            setTimer({...timer, seconds: timer.seconds - 1});
          }, 1000);
        } else if (!isActive && timer.seconds !== 0) {
          clearInterval(interval);
        } 
        return () => clearInterval(interval);
      }, [isActive, timer.seconds]);

      const addTime = () => {
        if (timer.seconds > timer.timeCap - 60) {
          setTimer({...timer, seconds: timer.timeCap})
        } else {
          setTimer({...timer, seconds: timer.seconds + ADD_TIME});
        }
      }

    return (
        <View style={styles.chart}>
            <ProgressCircle
              percent={ (timer.seconds / timer.timeCap) * 100 }
              radius={Math.ceil(Dimensions.get('window').height / 6)}
              borderWidth={Math.ceil(Dimensions.get('window').height / 30)}
              color={timerColor}
              shadowColor="#212121"
              bgColor="#303030"
            >
              <Text style={{ color: '#FFF', fontSize: 50 }}>{moment("2015-01-01").startOf('day').seconds(timer.seconds).format('m:ss')}</Text>
              <Text style={{ color: '#9E9E9E', fontSize: 20 }}>{(timer.timeCap == WORK_SECONDS) ? "Work Session" : "Break"}</Text>
            </ProgressCircle>
        </View>
    );
}

【问题讨论】:

  • 什么是interval?我没有看到它是在哪里声明的。
  • 你在 setting interval 那里,但它是在哪里声明的?与time 相同;我没有看到你的变量是在哪里声明的。
  • 对不起,我暂时搬了。它应该被声明为这个 let interval = null;就在 useEffect() 下时间被声明为全局变量 let time = 0;
  • 如果可能的话,将整个组件全部放在一个代码块中会很有帮助,包括渲染部分,以防出现问题。
  • isActive 也是全球性的?我怀疑您的问题源于使用全局变量而不是状态。仍在试图弄清楚一切是如何联系在一起的,但也许您正在更新一些全局变量并且没有任何东西触发您的重新渲染或效果。将所有内容放在一起会使这更易于分析。

标签: javascript reactjs react-native react-hooks setinterval


【解决方案1】:

我怀疑您的问题是您在调用 setTimer 时可能使用了过时状态。在您的useEffect 中,您正在创建一个在函数闭包中引用timersetInterval 函数。每次调用 setInterval 函数时,您都会得到相同的 timer 变量,因此调用:

setTimer({...timer, seconds: timer.seconds - 1});

...将在您创建间隔时将seconds 连续设置为比timer.seconds 的值小1 秒。

幸运的是,有一个简单的解决方法:

setTimer(timer => ({...timer, seconds: timer.seconds - 1}));

这将在每次执行更新之前获取最新的timer 状态。

【讨论】:

  • 这似乎解决了我目前的问题,谢谢!但是,我只希望它在 isActive 为真时运行。如果我检查 isActive 是否为真,我的代码永远不会触发。
  • 经过进一步检查,当我进入应用程序时通过 console.log() 读取 isActive 并且“active”为 true 时,isActive 的值被读取为 false。
  • 经过进一步的工作,我得到了这个功能!我必须在我的 useEffect 中添加 isActive 来设置我的事件监听器。
猜你喜欢
  • 2020-08-18
  • 1970-01-01
  • 2022-10-07
  • 2014-05-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多