【问题标题】:Countdown timer in ReactReact 中的倒数计时器
【发布时间】: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 来自问题,你觉得什么有意义? (看看我是否可以为解释不太清楚的事情添加更多知识)

标签: javascript reactjs countdown


【解决方案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 包进行倒计时。

      1. 安装包

        npm install react-countdown --save

        yarn add react-countdown

      2. 将包导入到您的文件中

        import Countdown from 'react-countdown';

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

        &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://stackoverflow.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,所以必须进一步调试
            • @FabianSchultz 您的解决方案很棒。构建倒数计时器组件并开始使用对我很有帮助。代码非常干净。继续努力!
            【解决方案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 });
            };
            

            【讨论】:

            • 啊,是的,我确实忘记绑定了。这并不能解决我没有倒计时的主要问题?
            猜你喜欢
            • 1970-01-01
            • 2019-10-20
            • 2020-09-29
            • 1970-01-01
            • 2019-11-29
            • 1970-01-01
            • 2012-11-08
            相关资源
            最近更新 更多