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';
在渲染方法中调用导入的“倒计时”并传递日期
<Countdown date=new Date('2021-09-26T10:05:29.896Z').getTime()>
或
<Countdown date=new Date("Sat Sep 26 2021")>
这里有一个例子。
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
参数中使用null
到setInterval
!【参考方案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 中的倒数计时器的主要内容,如果未能解决你的问题,请参考以下文章