React 中的倒数计时器

Posted

技术标签:

【中文标题】React 中的倒数计时器【英文标题】:Countdown timer in React 【发布时间】:2017-04-14 15:10:15 【问题描述】:

我在 javascript 中看到了很多倒数计时器,并希望在 React 中使用一个。

我借用了我在网上找到的这个功能:

secondsToTime(secs)
    let hours = Math.floor(secs / (60 * 60));

    let divisor_for_minutes = secs % (60 * 60);
    let minutes = Math.floor(divisor_for_minutes / 60);

    let divisor_for_seconds = divisor_for_minutes % 60;
    let seconds = Math.ceil(divisor_for_seconds);

    let obj = 
        "h": hours,
        "m": minutes,
        "s": seconds
    ;
    return obj;
  ;

然后我自己写了这段代码

  initiateTimer = () => 
    let timeLeftVar = this.secondsToTime(60);
    this.setState( timeLeft: timeLeftVar )
  ;

  startTimer = () => 
    let interval = setInterval(this.timer, 1000);
    this.setState( interval: interval );
  ;

  timer = () => 
    if (this.state.timeLeft >0)
      this.setState( timeLeft: this.state.timeLeft -1 );
    
    else 
      clearInterval(this.state.interval);
      //this.postToSlack();
    
  ;

目前点击它会将屏幕上的时间设置为:Time Remaining: 1 m : 0 s 但它不会将其减少到 Time Remaining: 0 m : 59 s 然后 Time Remaining: 0 m : 58 s 等等等等

我想我需要使用不同的参数再次调用该函数。我该怎么做呢?

编辑:我忘了说,我想要这个功能,以便我可以使用秒到分和秒

【问题讨论】:

the React documentation examples 之一是一个可以自我更新的时钟,看起来它会相当有用... @T.J.Crowder 这是半有用的。他们只是得到一个时间,虽然可以通过 componentDidMount 返回它,而我只想从起始位置提取秒和分钟.. 也许您可以使用 Stack Snippets 在问题中添加一个可运行的 minimal reproducible example support React and JSX,这样我们就可以看到问题的实际效果。 @T.J.Crowder 发现在 JSfiddle 中创建一个非常困难,因为我在许多文件中使用了许多带有许多道具的组件 @T.J.Crowder 来自问题,你觉得什么有意义? (看看我是否可以为解释不太清楚的事情添加更多知识) 【参考方案1】:

在本机反应中:

用法:

时间戳属性必须以秒为单位

const refTimer = useRef();
  
  const timerCallbackFunc = timerFlag => 
    // Setting timer flag to finished
    console.warn(
      'You can alert the user by letting him know that Timer is out.',
    );
  ;
    
    
 <Timer
 ref=refTimer
 timestamp=moment(item?.time_left).diff(moment(), 'seconds')
 timerCallback=timerCallbackFunc
 textStyle=styles.timerTextAHL
 />

Timer.js

import React, 
  useState,
  useEffect,
  useRef,
  forwardRef,
  useImperativeHandle,
 from 'react';
import  Text, View  from 'react-native';

const Timer = forwardRef((props, ref) => 
  // For Total seconds
  const [timeStamp, setTimeStamp] = useState(
    props.timestamp ? props.timestamp : 0,
  );
  // Delay Required
  const [delay, setDelay] = useState(props.delay ? props.delay : 1000);

  // Flag for informing parent component when timer is over
  const [sendOnce, setSendOnce] = useState(true);

  // Flag for final display time format
  const [finalDisplayTime, setFinalDisplayTime] = useState('');

  useInterval(() => 
    if (timeStamp > 0) 
      setTimeStamp(timeStamp - 1);
     else if (sendOnce) 
      if (props.timerCallback) 
        props.timerCallback(true);
       else 
        console.log('Please pass a callback function...');
      
      setSendOnce(false);
    
    setFinalDisplayTime(secondsToDhms(timeStamp));
  , delay);

  function secondsToDhms(seconds) 
    seconds = Number(seconds);
    var d = Math.floor(seconds / (3600 * 24));
    var h = Math.floor((seconds % (3600 * 24)) / 3600);
    var m = Math.floor((seconds % 3600) / 60);
    var s = Math.floor(seconds % 60);

    var dDisplay = d > 0 ? d + 'd ' : '';
    var hDisplay = h > 0 ? h + 'h ' : '';
    var mDisplay = m > 0 ? m + 'm ' : '';
    var sDisplay = s > 0 ? s + 's ' : '';
    return dDisplay + hDisplay + mDisplay + sDisplay;
  

  const refTimer = useRef();
  useImperativeHandle(ref, () => (
    resetTimer: () => 
      // Clearing days, hours, minutes and seconds
      // Clearing Timestamp
      setTimeStamp(props.timestamp);
      setSendOnce(true);
    ,
  ));

  return (
    <View ref=refTimer style=props.containerStyle>
      <Text style=props.textStyle>sendOnce ? finalDisplayTime : '0'</Text>
    </View>
  );
);

function useInterval(callback, delay) 
  const savedCallback = useRef();

  // Remember the latest function.
  useEffect(() => 
    savedCallback.current = callback;
  , [callback]);

  // Set up the interval.
  useEffect(() => 
    function tick() 
      savedCallback.current();
    
    if (delay !== null) 
      const id = setInterval(tick, delay);
      return () => 
        clearInterval(id);
      ;
    
  , [delay]);


export default Timer;

【讨论】:

【参考方案2】:

这是一个 TypeScript 版本的 React 倒数计时器。我使用了 Masood 和 M.Georgiev 兄弟的代码

import React, useState, useEffect, useCallback from "react";

const Minute_to_Seconds = 60;
const Seconds_to_milliseconds = 1000;

export interface CounterProps 
    minutes:number,
    statusAlert: (status: string)=>void,



export interface TimerProps 

    initialMinute: number,
    initialSeconds: number,


const Counter: React.FC<CounterProps> = (props) => 

    const convert_Minutes_To_MiliSeconds = (minute:number) => 

        return  minute * Minute_to_Seconds * Seconds_to_milliseconds;
    

    const convert_Mili_Seconds_To_Hour = (miliseconds:number) => 

        return new Date(miliseconds).toISOString().slice(11, -5);
    

    const convert_Mili_Seconds_To_Minute = (miliseconds:number) => 

        return new Date(miliseconds).toISOString().slice(11, -5);
    

    const [timer_State, setTimer_State]=useState(0);

    const [timerCount, setTimerCount] = useState(convert_Minutes_To_MiliSeconds(props.minutes));

    useEffect(() => 


        if (timerCount > 0) 

            const interval = setInterval(() => 

                    if (timer_State === 0) 

                        props.statusAlert("start");
                        setTimer_State(1);
                    


                    let tempTimerCount = timerCount;
                    tempTimerCount -= Seconds_to_milliseconds;
                    setTimerCount(tempTimerCount);
                ,
                (timer_State === 0)
                    ? 0

                    : Seconds_to_milliseconds

            );
            return () => 


                clearInterval(interval);
            


        
        else

            props.statusAlert("end");
        



    , [

        timer_State,
        timerCount,
        props,
    ]);

    return (
        <p>
            Time left: convert_Mili_Seconds_To_Hour(timerCount)
        </p>
    );




const Timer: React.FC<TimerProps> = (props) => 

    const [ minutes, setMinutes ] = useState(props.initialMinute);
    const [seconds, setSeconds ] =  useState(props.initialSeconds);

    useEffect(()=>
        const myInterval = setInterval(() => 
            if (seconds > 0) 
                setSeconds(seconds - 1);
            
            if (seconds === 0) 
                if (minutes === 0) 
                    clearInterval(myInterval)
                 else 
                    setMinutes(minutes - 1);
                    setSeconds(59);
                
            
        , 1000)
        return ()=> 
            clearInterval(myInterval);
        ;
    );

    return (
        <div>
             ((minutes === 0) && (seconds === 0))
                ? "Press F5 to Refresh"
                : <h1> minutes:seconds < 10 ?  `0$seconds` : seconds</h1>
            
        </div>
    )



const RCTAPP=()=> 

    const status_Alert2=(status: string)=> 

        console.log("__________________________==================== status: ", status);
        if (status==="start")
            alert("Timer started");
        
        else
            alert("Time's up");
        
    

    return (
        <div style=textAlign: "center">

            <Counter
                minutes=1
                // minutes=0.1
                statusAlert=status_Alert2
            />

            <Timer
                initialMinute=0
                initialSeconds=30
            />

        </div>

    );



export default RCTAPP;

【讨论】:

【参考方案3】:

我遇到了同样的问题,我找到了这个 npm 包进行倒计时。

    安装包

    npm install react-countdown --save

    yarn add react-countdown

    将包导入到您的文件中

    import Countdown from 'react-countdown';

    在渲染方法中调用导入的“倒计时”并传递日期

    &lt;Countdown date=new Date('2021-09-26T10:05:29.896Z').getTime()&gt;

    &lt;Countdown date=new Date("Sat Sep 26 2021")&gt;

这里有一个例子。

import React from "react";
import ReactDOM from "react-dom";
import Countdown from "react-countdown";

// Random component
const Completionist = () => <span>You are good to go!</span>;

ReactDOM.render(
  <Countdown date=new Date('2021-09-26T10:05:29.896Z').getTime()>
    <Completionist />
  </Countdown>,
  document.getElementById("root")
);

详细文档可以看这里https://www.npmjs.com/package/react-countdown

【讨论】:

【参考方案4】:

当您使用功能组件时,上面的代码是一个不错的选择:

import React,  useState, useEffect  from "react";
import  MessageStrip  from "@ui5/webcomponents-react";
import "./Timer.scss";

const nMinuteSeconds = 60;
const nSecondInMiliseconds = 1000;

const convertMinutesToMiliseconds = (minute) =>
  minute * nMinuteSeconds * nSecondInMiliseconds;

const convertMilisecondsToHour = (miliseconds) => new Date(miliseconds).toISOString().slice(11, -5);

export default function Counter( minutes, onTimeOut ) 
  let [timerCount, setTimerCount] = useState(
    convertMinutesToMiliseconds(minutes)
  );
  let interval;

  useEffect(() => 
    if (interval) 
      clearInterval(interval);
    

    interval = setInterval(() => 
      if (timerCount === 0 && interval) 
        onTimeOut();
        clearInterval(interval);
      

      setTimerCount((timerCount -= nSecondInMiliseconds));
    , nSecondInMiliseconds);
  , []);

  return (
    <>
      <MessageStrip design="Information" hide-close-button>
        Time left: convertMilisecondsToHour(timerCount)
      </MessageStrip>
    </>
  );

【讨论】:

【参考方案5】:

功能: 1)开始 2)重置

功能组件

import useState, useCallback from 'react';
const defaultCount = 10;
const intervalGap = 300;

const Counter = () => 
    const [timerCount, setTimerCount] = useState(defaultCount);
    
    const startTimerWrapper = useCallback((func)=>
        let timeInterval: NodeJS.Timer;
        return () => 
            if(timeInterval) 
                clearInterval(timeInterval)
            
            setTimerCount(defaultCount)
            timeInterval = setInterval(() => 
                func(timeInterval)
            , intervalGap)
        
    , [])

    const timer = useCallback(startTimerWrapper((intervalfn: NodeJS.Timeout) => 
         setTimerCount((val) => 
            if(val === 0 ) 
                clearInterval(intervalfn);
                return val
             
            return val - 1
        )
    ), [])

    return <>
        <div> Counter App</div>
        <div> <button onClick=timer>Start/Reset</button></div>
        <div> timerCount</div>
    </>

export default Counter;

【讨论】:

如何引用 NodeJS.Timer ?这给了我例外。【参考方案6】:

这是一个简单的实现,使用钩子和@dan-abramov 的 useInterval 实现

import React, useState, useEffect, useRef from 'react'
import './styles.css'

const STATUS = 
  STARTED: 'Started',
  STOPPED: 'Stopped',


const INITIAL_COUNT = 120

export default function CountdownApp() 
  const [secondsRemaining, setSecondsRemaining] = useState(INITIAL_COUNT)
  const [status, setStatus] = useState(STATUS.STOPPED)

  const secondsToDisplay = secondsRemaining % 60
  const minutesRemaining = (secondsRemaining - secondsToDisplay) / 60
  const minutesToDisplay = minutesRemaining % 60
  const hoursToDisplay = (minutesRemaining - minutesToDisplay) / 60

  const handleStart = () => 
    setStatus(STATUS.STARTED)
  
  const handleStop = () => 
    setStatus(STATUS.STOPPED)
  
  const handleReset = () => 
    setStatus(STATUS.STOPPED)
    setSecondsRemaining(INITIAL_COUNT)
  
  useInterval(
    () => 
      if (secondsRemaining > 0) 
        setSecondsRemaining(secondsRemaining - 1)
       else 
        setStatus(STATUS.STOPPED)
      
    ,
    status === STATUS.STARTED ? 1000 : null,
    // passing null stops the interval
  )
  return (
    <div className="App">
      <h1>React Countdown Demo</h1>
      <button onClick=handleStart type="button">
        Start
      </button>
      <button onClick=handleStop type="button">
        Stop
      </button>
      <button onClick=handleReset type="button">
        Reset
      </button>
      <div style=padding: 20>
        twoDigits(hoursToDisplay):twoDigits(minutesToDisplay):
        twoDigits(secondsToDisplay)
      </div>
      <div>Status: status</div>
    </div>
  )


// source: https://overreacted.io/making-setinterval-declarative-with-react-hooks/
function useInterval(callback, delay) 
  const savedCallback = useRef()

  // Remember the latest callback.
  useEffect(() => 
    savedCallback.current = callback
  , [callback])

  // Set up the interval.
  useEffect(() => 
    function tick() 
      savedCallback.current()
    
    if (delay !== null) 
      let id = setInterval(tick, delay)
      return () => clearInterval(id)
    
  , [delay])


// https://***.com/a/2998874/1673761
const twoDigits = (num) => String(num).padStart(2, '0')

这里是codeandbox的实现:https://codesandbox.io/s/react-countdown-demo-gtr4u?file=/src/App.js

【讨论】:

谢谢,帮了我很多忙!只是:不要在ms 参数中使用nullsetInterval【参考方案7】:

用户输入倒计时

界面截图

import React,  Component  from 'react';
import './App.css';

class App extends Component 
  constructor() 
    super();
    this.state = 
      hours: 0,
      minutes: 0,
      seconds:0
    
    this.hoursInput = React.createRef();
    this.minutesInput= React.createRef();
    this.secondsInput = React.createRef();
  

  inputHandler = (e) => 
    this.setState([e.target.name]: e.target.value);
  

  convertToSeconds = ( hours, minutes,seconds) => 
    return seconds + minutes * 60 + hours * 60 * 60;
  

  startTimer = () => 
    this.timer = setInterval(this.countDown, 1000);
  

  countDown = () => 
    const   hours, minutes, seconds  = this.state;
    let c_seconds = this.convertToSeconds(hours, minutes, seconds);

    if(c_seconds) 

      // seconds change
      seconds ? this.setState(seconds: seconds-1) : this.setState(seconds: 59);

      // minutes change
      if(c_seconds % 60 === 0 && minutes) 
        this.setState(minutes: minutes -1);
      

      // when only hours entered
      if(!minutes && hours) 
        this.setState(minutes: 59);
      

      // hours change
      if(c_seconds % 3600 === 0 && hours) 
        this.setState(hours: hours-1);
      

     else 
      clearInterval(this.timer);
    
  


  stopTimer = () => 
    clearInterval(this.timer);
  

  resetTimer = () => 
    this.setState(
      hours: 0,
      minutes: 0,
      seconds: 0
    );
    this.hoursInput.current.value = 0;
    this.minutesInput.current.value = 0;
    this.secondsInput.current.value = 0;
  


  render() 
    const  hours, minutes, seconds  = this.state;

    return (
      <div className="App">
         <h1 className="title"> (( React Countdown )) </h1>
         <div className="inputGroup">
            <h3>Hrs</h3>
            <input ref=this.hoursInput type="number" placeholder=0  name="hours"  onChange=this.inputHandler />
            <h3>Min</h3>
            <input  ref=this.minutesInput type="number"  placeholder=0   name="minutes"  onChange=this.inputHandler />
            <h3>Sec</h3>
            <input   ref=this.secondsInput type="number"  placeholder=0  name="seconds"  onChange=this.inputHandler />
         </div>
         <div>
            <button onClick=this.startTimer className="start">start</button>
            <button onClick=this.stopTimer  className="stop">stop</button>
            <button onClick=this.resetTimer  className="reset">reset</button>
         </div>
         <h1> Timer hours: minutes : seconds </h1>
      </div>

    );
  


export default App;

【讨论】:

你好,如何在时间间隔为零后调用函数【参考方案8】:

显示使用 Date.now() 倒计时的基本概念,而不是减去会随时间漂移的倒计时。

class Example extends React.Component 
  constructor() 
    super();
    this.state = 
      time: 
        hours: 0,
        minutes: 0,
        seconds: 0,
        milliseconds: 0,
      ,
      duration: 2 * 60 * 1000,
      timer: null
    ;
    this.startTimer = this.start.bind(this);
  

  msToTime(duration) 
    let milliseconds = parseInt((duration % 1000));
    let seconds = Math.floor((duration / 1000) % 60);
    let minutes = Math.floor((duration / (1000 * 60)) % 60);
    let hours = Math.floor((duration / (1000 * 60 * 60)) % 24);

    hours = hours.toString().padStart(2, '0');
    minutes = minutes.toString().padStart(2, '0');
    seconds = seconds.toString().padStart(2, '0');
    milliseconds = milliseconds.toString().padStart(3, '0');

    return 
      hours,
      minutes,
      seconds,
      milliseconds
    ;
  

  componentDidMount() 

  start() 
    if (!this.state.timer) 
      this.state.startTime = Date.now();
      this.timer = window.setInterval(() => this.run(), 10);
    
  

  run() 
    const diff = Date.now() - this.state.startTime;
    
    // If you want to count up
    // this.setState(() => (
    //  time: this.msToTime(diff)
    // ));
    
    // count down
    let remaining = this.state.duration - diff;
    if (remaining < 0) 
      remaining = 0;
    
    this.setState(() => (
      time: this.msToTime(remaining)
    ));
    if (remaining === 0) 
      window.clearTimeout(this.timer);
      this.timer = null;
    
  

  render() 
    return ( <
      div >
      <
      button onClick = 
        this.startTimer
       > Start < /button> 
        this.state.time.hours
      : 
        this.state.time.minutes
      : 
        this.state.time.seconds
      . 
        this.state.time.milliseconds
      :
      <
      /div>
    );
  


ReactDOM.render( < Example / > , document.getElementById('View'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="View"></div>

【讨论】:

这是一种更稳定的方法。然而问题是,暂停和继续更具挑战性。 @Tosh 不是真的......你知道它被暂停时经过了多少时间,你存储它。继续您找出差异并设置新的开始时间。【参考方案9】:

这是一个使用钩子的解决方案,Timer 组件,我正在用钩子复制上面相同的逻辑

import React from 'react'
import  useState, useEffect  from 'react';

const Timer = (props:any) => 
    const initialMinute = 0,initialSeconds = 0 = props;
    const [ minutes, setMinutes ] = useState(initialMinute);
    const [seconds, setSeconds ] =  useState(initialSeconds);
    useEffect(()=>
    let myInterval = setInterval(() => 
            if (seconds > 0) 
                setSeconds(seconds - 1);
            
            if (seconds === 0) 
                if (minutes === 0) 
                    clearInterval(myInterval)
                 else 
                    setMinutes(minutes - 1);
                    setSeconds(59);
                
             
        , 1000)
        return ()=> 
            clearInterval(myInterval);
          ;
    );

    return (
        <div>
         minutes === 0 && seconds === 0
            ? null
            : <h1> minutes:seconds < 10 ?  `0$seconds` : seconds</h1> 
        
        </div>
    )


export default Timer;

【讨论】:

你每隔一秒初始化并清除间隔,我认为最好把空数组作为useEffect的依赖【参考方案10】:

class Example extends React.Component 
  constructor() 
    super();
    this.state =  time: , seconds: 5 ;
    this.timer = 0;
    this.startTimer = this.startTimer.bind(this);
    this.countDown = this.countDown.bind(this);
  

  secondsToTime(secs)
    let hours = Math.floor(secs / (60 * 60));

    let divisor_for_minutes = secs % (60 * 60);
    let minutes = Math.floor(divisor_for_minutes / 60);

    let divisor_for_seconds = divisor_for_minutes % 60;
    let seconds = Math.ceil(divisor_for_seconds);

    let obj = 
      "h": hours,
      "m": minutes,
      "s": seconds
    ;
    return obj;
  

  componentDidMount() 
    let timeLeftVar = this.secondsToTime(this.state.seconds);
    this.setState( time: timeLeftVar );
  

  startTimer() 
    if (this.timer == 0 && this.state.seconds > 0) 
      this.timer = setInterval(this.countDown, 1000);
    
  

  countDown() 
    // Remove one second, set state so a re-render happens.
    let seconds = this.state.seconds - 1;
    this.setState(
      time: this.secondsToTime(seconds),
      seconds: seconds,
    );
    
    // Check if we're at zero.
    if (seconds == 0)  
      clearInterval(this.timer);
    
  

  render() 
    return(
      <div>
        <button onClick=this.startTimer>Start</button>
        m: this.state.time.m s: this.state.time.s
      </div>
    );
  


ReactDOM.render(<Example/>, document.getElementById('View'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="View"></div>

【讨论】:

【参考方案11】:

您必须在剩余的秒数内每秒setState(每次调用间隔)。这是一个例子:

class Example extends React.Component 
  constructor() 
    super();
    this.state =  time: , seconds: 5 ;
    this.timer = 0;
    this.startTimer = this.startTimer.bind(this);
    this.countDown = this.countDown.bind(this);
  

  secondsToTime(secs)
    let hours = Math.floor(secs / (60 * 60));

    let divisor_for_minutes = secs % (60 * 60);
    let minutes = Math.floor(divisor_for_minutes / 60);

    let divisor_for_seconds = divisor_for_minutes % 60;
    let seconds = Math.ceil(divisor_for_seconds);

    let obj = 
      "h": hours,
      "m": minutes,
      "s": seconds
    ;
    return obj;
  

  componentDidMount() 
    let timeLeftVar = this.secondsToTime(this.state.seconds);
    this.setState( time: timeLeftVar );
  

  startTimer() 
    if (this.timer == 0 && this.state.seconds > 0) 
      this.timer = setInterval(this.countDown, 1000);
    
  

  countDown() 
    // Remove one second, set state so a re-render happens.
    let seconds = this.state.seconds - 1;
    this.setState(
      time: this.secondsToTime(seconds),
      seconds: seconds,
    );
    
    // Check if we're at zero.
    if (seconds == 0)  
      clearInterval(this.timer);
    
  

  render() 
    return(
      <div>
        <button onClick=this.startTimer>Start</button>
        m: this.state.time.m s: this.state.time.s
      </div>
    );
  


ReactDOM.render(<Example/>, document.getElementById('View'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="View"></div>

【讨论】:

这看起来不错。一个问题是它不会停在 0 并且会变为负数?解决这个问题,我会接受的;) 嗯,它类似于您在初始代码中的内容。检查是否还有几秒钟,然后执行clearInterval。更新了我的答案。 您还可以进行更多优化,例如重置计时器、暂停等,但问题的目标是如何倒计时并将其反映在渲染中。 干杯,由于某些奇怪的原因,我的仍然是负数。我什至 console.logged(seconds) 显示它为 0,所以必须进一步调试 @F***Schultz 您的解决方案很棒。构建倒数计时器组件并开始使用对我很有帮助。代码非常干净。继续努力!【参考方案12】:

setInterval 的一个缺点是它会减慢主线程的速度。您可以使用requestAnimationFrame 来设置倒数计时器来防止这种情况发生。例如,这是我的通用倒数计时器组件:

class Timer extends Component 
  constructor(props) 
    super(props)
    // here, getTimeRemaining is a helper function that returns an 
    // object with  total, seconds, minutes, hours, days 
    this.state =  timeLeft: getTimeRemaining(props.expiresAt) 
  

  // Wait until the component has mounted to start the animation frame
  componentDidMount() 
    this.start()
  

  // Clean up by cancelling any animation frame previously scheduled
  componentWillUnmount() 
    this.stop()
  

  start = () => 
    this.frameId = requestAnimationFrame(this.tick)
  

  tick = () => 
    const timeLeft = getTimeRemaining(this.props.expiresAt)
    if (timeLeft.total <= 0) 
      this.stop()
      // ...any other actions to do on expiration
     else 
      this.setState(
         timeLeft ,
        () => this.frameId = requestAnimationFrame(this.tick)
      )
    
  

  stop = () => 
    cancelAnimationFrame(this.frameId)
  

  render() ...

【讨论】:

不错!但我认为您可以通过防止太多渲染来进行优化。您不必每帧都 setState (并重新渲染)(每秒约 30 帧)。只有当timeLeft(以秒为单位)发生变化时,您才能setState。也许使用 shouldComponentUpdate ?我说的对吗?【参考方案13】:

问题在于您的“this”值。 计时器函数无法访问“状态”道具,因为在不同的上下文中运行。我建议你这样做:

...
startTimer = () => 
  let interval = setInterval(this.timer.bind(this), 1000);
  this.setState( interval );
;

如您所见,我为您的计时器功能添加了“绑定”方法。这允许计时器在调用时访问您的反应组件的相同“this”(这是一般使用 javascript 时的主要问题/改进)。

另一种选择是使用另一个箭头函数:

startTimer = () => 
  let interval = setInterval(() => this.timer(), 1000);
  this.setState( interval );
;

【讨论】:

啊,是的,我确实忘记绑定了。这并不能解决我没有倒计时的主要问题?

以上是关于React 中的倒数计时器的主要内容,如果未能解决你的问题,请参考以下文章

在 React 中使用 Hooks 实现倒数计时器

管理员单击按钮后为所有用户启动倒数计时器(react、redux、socket.io)

Pygame中的倒数计时器

MVC 中的倒数计时器

GMT/UTC 中的 Javascript 倒数计时器

当活动暂停时,如何正确停止倒数计时器中的媒体播放器?