useEffect() 在尝试更新状态时导致奇怪的行为

useEffect() causing strange behaviour when trying to update state

我正在制作一个我认为非常简单的程序,它允许您跟踪 men/women 在会议中发言的时间以及他们发言的次数。但是发生了一些奇怪的事情 - 它部署在这里所以你可以看到:https://make-space.netlify.app/ 如果您在没有计时器 运行 的情况下点击 +1 增量按钮 ,它可以正常工作并且完美地增加。然而,如果你有定时器 运行 那么它会上升,然后下降,然后在大约一秒内再次上升。

从控制台日志记录到尝试找出问题所在,我很确定这是我用来处理计时器的 useEffect() 函数,但我无法弄清楚如何以不同的方式进行处理。

我正在更新的是“speakerCount”,它在页面的下方呈现没有问题,但是当试图将它传递给其他组件时会导致问题。

本节中:

const incrementSpeakerCount = () => {
    difference(minutes, seconds, speakerCount + 1);
    setSpeakerCount(speakerCount + 1);
  };

如果我输入 console.log(speakerCount) 它不显示更新。

“差异”道具是我从 parent 组件传递的函数,用于获取状态并将其传递下去。

完整代码在此处,但整个内容可在 github 此处获得:https://github.com/gordonmaloney/makespace

import React, { useState, useEffect } from "react";
import { Button } from "@material-ui/core";

const Timer = ({ gender, difference }) => {
  const [seconds, setSeconds] = useState(0);
  const [minutes, setMinutes] = useState(0);
  const [isActive, setIsActive] = useState(false);
  const [speakerCount, setSpeakerCount] = useState(0);

  let speakerCountUpdated = speakerCount

  function toggle() {
    setIsActive(!isActive);
  }

  function reset() {
    difference(0, 0, 0);
    setSeconds(0);
    setMinutes(0);
    setSpeakerCount(0);
    setIsActive(false);
  }

  if (seconds === 60) {
    setSeconds(0);
    setMinutes(minutes + 1);
  }

  useEffect(() => {
    let interval = null;
    if (isActive) {
      interval = setInterval(() => {
        setSeconds((seconds) => seconds + 1);
        difference(minutes, seconds, speakerCount)
      }, 1000);
    } else if (!isActive && seconds !== 0) {
      clearInterval(interval);
    }
    return () => clearInterval(interval);
  }, [isActive, seconds]);


  const incrementSpeakerCount = () => {
    difference(minutes, seconds, speakerCount + 1);
    setSpeakerCount(speakerCount + 1);
  };

  return (
    <div>
      <center>
        <div className={"timerContainer"}>
          <div>

            <b>{gender}</b> have been speaking for:
            <br />
            {minutes} {minutes === 1 ? "minute" : "minutes"} and {seconds}{" "}
            {seconds === 1 ? "second" : "seconds"}

          </div>
          <div>
            <Button
              className={`button button-primary button-primary-${
                isActive ? "active" : "inactive"
              }`}
              variant="contained"
              color="primary"
              size="small"
              onClick={toggle}
            >
              {isActive ? "Pause" : "Start"}
            </Button>
            <br />
            <br />
            <b>
              {speakerCount === 0 ? "No" : speakerCount}{" "}
              {speakerCount !== 1
                ? gender.toLowerCase()
                : gender === "Men"
                ? "man"
                : "woman"}
            </b>{" "}
            {speakerCount !== 1 ? "have" : "has"} spoken.
            <br />
            <Button
              className="button"
              variant="contained"
              color="primary"
              size="small"
              onClick={incrementSpeakerCount}
            >
              + 1
            </Button>
            <br />
            <br />
            <Button
              className="button"
              variant="contained"
              size="small"
              onClick={reset}
            >
              Reset
            </Button>
          </div>
        </div>
      </center>
    </div>
  );
};

export default Timer;

非常欢迎任何想法 - 我对生命周期方法比较陌生,所以怀疑我可能做错了一些非常简单的事情。

非常感谢!

这是一个有趣的问题。您的计时器每秒被调用一次。但是缺少 speakerCount 的依赖项。因此理论上 speakerCount 可能由于 setInterval.

而滞后

想象一下时间从 0 到 1,在那段时间里,您单击了按钮,但 speakerCount 仍保留当前渲染中的先前值。请记住,如果没有渲染,speakerCount 不能持有新值。

  useEffect(() => {
    let interval = null;
    if (isActive) {
      interval = setInterval(() => {
        setSeconds((seconds) => seconds + 1);
        difference(minutes, seconds, speakerCount)
      }, 1000);
    } else if (!isActive && seconds !== 0) {
      clearInterval(interval);
    }
    return () => clearInterval(interval);
  }, [isActive, seconds, speakerCount]);

如果上面的方法有效,请尝试,在你的情况下你实际上不能准确地得到 1s,因为每次当 isActivesecondsspeakerCount 改变你试图拍摄到再过一秒钟。所以不知何故,即使它在工作,也不能正好是 1 秒。