reactjs 更新React中的状态导致setInterval函数产生异常结果

xxls0lw8  于 2023-06-22  发布在  React
关注(0)|答案(1)|浏览(152)

我正在尝试创建秒表功能。我正在使用setInterval函数来每1000 ms递增定时器变量。在这一点上一切都很好。但是,当我尝试将需要更新的状态包含到setInterval函数中时;它破坏了代码,并开始笨拙和不一致地打印。
我把我的代码写在下面。我注解掉了导致错误的行。在我取消注解这些行之前,一切都运行得很好。我真的需要弄明白到底发生了什么。

import { useState } from "react"

function Timer() {
  const [time, setTime] = useState({
    hr: 0,
    min: 0,
    sec: 0
  })
  const timeCalc = {
    seconds: 0,
    minutes: 0,
    hours: 0,
    timeCount() {
  let interval = setInterval(() => {
      this.seconds++
      console.log(this.seconds)
      // setTime({ ...time, sec: this.seconds })
      if (this.seconds === 60) {
        this.seconds = 0
        // setTime({ ...time, sec: 0 })
        this.minutes++
        // setTime({ ...time, min: this.minutes })
      }
      if (this.minutes === 60) {
        this.minutes = 0
        // setTime({ ...time, min: 0 })
        this.hours++
        // setTime({ ...time, hr: this.hours })
      }
    }, 1000)    
   }
  }
    timeCalc.timeCount()
  

  return (
    <div>
      <span>{`00${time.hr} : `.slice(-5)}</span>
      <span>{`00${time.min} : `.slice(-5)}</span>
      <span>{`00${time.sec}`.slice(-2)}</span>
    </div>
  )
}
export default Timer
xlpyo6sf

xlpyo6sf1#

为了使这工作,您的代码存在一些问题

  • 你的interval在每次状态改变时执行,你需要在timeCalc中放入useEffect。timeCount()执行一次
  • 这个变化需要你注意useEffect中的依赖关系,因为我们需要在我们传递一个空数组给useEffect时执行它,但是linter抱怨说timeCalc是一个依赖关系,我们需要添加它,但是作为一个对象,每次它计算prev object === current object时都会执行useEffect(因为它总是不同的)所以我们需要把timeCalc放在useEffect中(检查下面的代码)
  • 一旦我们将timeCalc对象移动到useEffect中,useEffect(linter)抱怨“time”状态在useEffect之外,我们可以添加时间来使用效果,但对象不是传递给依赖关系数组的好类型,因为它们总是不同的(即使它们具有相同的属性),为了解决这个问题,我们可以向setTime添加一个函数更新,functional update是一个接收前一个状态的函数,你应该基于此返回新的状态(检查下面的代码)
  • 卸载组件时清除间隔是一个很好的做法(这是防止意外行为所必需的)(使用clearInterval在useEffect内部返回)

修复代码:

import { useState, useEffect } from "react";

function Timer() {
  const [time, setTime] = useState({
    hr: 0,
    min: 0,
    sec: 0,
  });

  useEffect(() => {
    const timeCalc = {
      seconds: 0,
      minutes: 0,
      hours: 0,
      timeCount: function() {
        console.log('executing');
        return setInterval(() => {
          this.seconds++;
          setTime((time) => ({ ...time, sec: this.seconds }));
          if (this.seconds === 60) {
            this.seconds = 0;
            setTime((time) => ({ ...time, sec: 0 }));
            this.minutes++;
            setTime((time) => ({ ...time, min: this.minutes }));
          }
          if (this.minutes === 60) {
            this.minutes = 0;
            setTime((time) => ({ ...time, min: 0 }));
            this.hours++;
            setTime((time) => ({ ...time, hr: this.hours }));
          }
        }, 1000);
      },
    };

    const intervalId = timeCalc.timeCount();

    return () => {
      clearInterval(intervalId)
    }
  }, []);

  return (
    <div>
      <span>{`00${time.hr} : `.slice(-5)}</span>
      <span>{`00${time.min} : `.slice(-5)}</span>
      <span>{`00${time.sec}`.slice(-2)}</span>
    </div>
  );
}
export default Timer;

下面是codesandbox https://codesandbox.io/s/dazzling-fire-68wjtf中的结果
只有一些与你的代码相关的建议

  • 如果可能的话,总是将对象拆分为不同的最小状态(例如,一个状态表示秒,一个表示分钟,一个表示小时)
  • 当你的代码有sideEffect(比如interval,setTimeout,setState after evaluation,fetch an API)并且你想立即执行它时,考虑使用useEffect
  • 记住useEffect中的依赖数组
  • 使用间隔时记得清除它们
  • 如果是一个函数组件,尽量不要使用this关键字,可能没有必要,相反,你可以使用const,let,states或refs

这将是遵循此建议后的重构

import { useState, useEffect } from "react";

function Timer() {
  const [seconds, setSeconds] = useState(0);
  const [minutes, setMinutes] = useState(0);
  const [hours, setHours] = useState(0);

  useEffect(() => {
    const intervalId = setInterval(() => {
      const newSeconds = seconds + 1;
      const newMinutes = minutes + 1;
      const newHours = hours + 1;
      setSeconds(newSeconds);
      if (newSeconds === 60) {
        setSeconds(0);
        setMinutes(newMinutes);
      }
      if (newMinutes === 60) {
        setMinutes(0);
        setHours(newHours);
      }
      if (newHours === 24) {
        setHours(0);
      }
    }, 1000);

    return () => {
      clearInterval(intervalId);
    };
  }, [seconds, minutes, hours]);

  return (
    <div>
      <span>{`00${hours} : `.slice(-5)}</span>
      <span>{`00${minutes} : `.slice(-5)}</span>
      <span>{`00${seconds}`.slice(-2)}</span>
    </div>
  );
}
export default Timer;

代码沙盒https://codesandbox.io/s/bold-mendel-fv3r2v?file=/src/App.js

相关问题