带有 pause/resume 计数器的 setTimeout 未在渲染时更新

setTimout with pause/resume counter not updating on render

我想设置一个可以在 React.js 中暂停和恢复的计数器。但是到目前为止,无论我尝试过什么,功能部分都在工作(pause/resume 正在工作),但它没有在渲染时更新计数器。下面是我的代码:

const ProgressBar = (props) => {
  const [isPlay, setisPlay] = useState(false);
  const [progressText, setProgressText] = useState(
    props.duration ? props.duration : 20
  );
  var elapsed,
    secondsLeft = 20;

  const timer = () => {
    // setInterval for every second
    var countdown = setInterval(() => {
      // if allowed time is used up, clear interval
      if (secondsLeft < 0) {
        clearInterval(countdown);
        return;
      }
      // if paused, record elapsed time and return
      if (isPlay === true) {
        elapsed = secondsLeft;
        return;
      }
      // decrement seconds left
      secondsLeft--;
      console.warn(secondsLeft);
    }, 1000);
  };
  timer();

  const stopProgress = () => {
    setisPlay(!isPlay);
    if (isPlay === false) {
      secondsLeft = elapsed;
    }
  };

  return (
    <>
      <p>{secondsLeft}</p>
    </>
  );
};

export default ProgressBar;

到目前为止,我已经尝试 React.js state、global var type、global let type、react ref 来使变量成为全局变量,但其中 none 有效..

那么基本上为什么您的示例不起作用? 您的 secondsLeft 变量未连接到您的 JSX。因此,每次您的组件重新渲染时,它都会创建一个值为 20 的新 secondsLeft 变量(因为重新渲染只是执行 returns JSX 的函数) 如何使您的变量值持久化 - useState or useReducer 钩子用于反应功能组件或基于 class 的状态。因此,React 将为您存储下一个重新渲染周期的所有值。 第二个问题是 React 不会重新渲染你的组件,它只是不知道什么时候应该重新渲染。那么是什么导致您的组件重新渲染 -

  • 道具变化
  • 状态变化
  • 上下文更改
  • adding/removing 您的组件来自 DOM
  • 也许我遗漏了一些其他案例

所以下面的例子对我来说很好

import { useEffect, useState } from "react";

function App() {
  const [pause, setPause] = useState(false);
  const [secondsLeft, setSecondsLeft] = useState(20);

  const timer = () => {
    var countdown = setInterval(() => {
      if (secondsLeft <= 0) {
        clearInterval(countdown);
        return;
      }

      if (pause === true) {
        clearInterval(countdown);
        return;
      }

      setSecondsLeft((sec) => sec - 1);
    }, 1000);
    return () => {
      clearInterval(countdown);
    };
  };
  useEffect(timer, [secondsLeft, pause]);

  const pauseTimer = () => {
    setPause((pause) => !pause);
  };

  return (
    <div>
      <span>Seconds Left</span>
      <p>{secondsLeft}</p>
      <button onClick={pauseTimer}>{pause ? "Start" : "Pause"}</button>
    </div>
  );
}
import React, { useState, useEffect } from "react";

import logo from "./logo.svg";
import "./App.css";
var timer = null;
function App() {
  const [counter, setCounter] = useState(0);
  const [isplayin, setIsPlaying] = useState(false);

  const pause = () => {
    setIsPlaying(false);
    clearInterval(timer);
  };

  const reset = () => {
    setIsPlaying(false);
    setCounter(0);
    clearInterval(timer);
  };

  const play = () => {
    setIsPlaying(true);
    timer = setInterval(() => {
      setCounter((prev) => prev + 1);
    }, 1000);
  };
  return (
    <div className="App">
      <p>Counter</p>
      <h1>{counter}</h1>
      {isplayin ? (
        <>
          <button onClick={() => pause()}>Pause</button>
          <button onClick={() => reset()}>Reset</button>
        </>
      ) : (
        <>
          {counter > 0 ? (
            <>
              <button onClick={() => play()}>Resume</button>
              <button onClick={() => reset()}>Reset</button>
            </>
          ) : (
            <button onClick={() => play()}>Start</button>
          )}
        </>
      )}
    </div>
  );
}

export default App;