我试图阻止计数器在页面刷新时重新加载。这是代码:
import React, { useState, useEffect } from "react";
import { useDispatch } from "react-redux";
import { timerEnd } from "./states-manager/timer-slice";
export default function Timer() {
// const lol =localStorage.getItem('timer')
const [countdown, setCountdown] = useState({
expirityTime: 100,
expired: false
});
const [timer, setTimer] = useState()
const [focus, setFocus] = useState(true)
const dispatch = useDispatch();
// Stop Counter when tab is switched
useEffect(()=>{
window.onblur = function () {
setFocus(false)
};
window.onfocus = function () {
setFocus(true)
setTimer(countdown.expirityTime)
};
})
useEffect(() => {
let time = countdown.expirityTime;
if (time > 0 && (focus)) {
const timerId = setTimeout(() => {
setCountdown({ expirityTime:time- 1 });
}, 1000);
return () => clearTimeout(timerId);
}
});
//Passing CountDown Vlaue to the Quiz.js through Redux Toolkit
dispatch(timerEnd(timer));
return (
<div className="App">
<div className="timer">
Time Remaining: {Math.floor(countdown.expirityTime / 60)}:
{countdown.expirityTime % 60}
</div>
</div>
);
}
我可以将倒计时时间存储在本地存储中,但由于代码的原因,它也会重新启动。如果你能帮我,请告诉我。如果你需要任何进一步的信息,请告诉我。
我做了一个react hook,它可以用作客户端的计时器或倒计时计时器,请在github上找到代码和示例:PersistentTimer
除了react之外,不需要依赖项。
或者你可以找到下面的代码:
import { useState, useEffect, useRef } from "react";
const initTimer = { //initial value of ref
lastSavedElapsedTime: 0,
elapsedTime: 0,
intervalId: null as ReturnType<typeof setInterval> | null,
start: 0,
manuallyPaused: false
}
interface OPTIONS {
updateFrequency?: number,
maximumValue?:number,
callback?:(()=>void)
LocalStorageItemName?:string
}
const defaultOptions:OPTIONS = {
updateFrequency: 1,
maximumValue:0,
callback:undefined,
LocalStorageItemName:'Persistant_timer'
}
const usePersistantTimer = (
pauseOnNoFocus: boolean = true, {
updateFrequency = 1,
maximumValue = 0,
callback,
LocalStorageItemName= 'Persistant_timer'}:OPTIONS = defaultOptions
): [number, () => void, () => void, () => void] => {
const timer = useRef(initTimer)
const cu = timer.current
const getValueFromLocalStorage = () => {
let v = parseInt(localStorage.getItem(LocalStorageItemName) || '0')
if (isNaN(v) || v < 0) v = 0
cu.lastSavedElapsedTime = v
cu.elapsedTime = 0
cu.start = new Date().getTime()
}
const [elapsedTime, setElapsedTime] = useState(cu.lastSavedElapsedTime)
const PIN = (i: number) => { // set parameter to default 1 if the paramenter is not legal number.
return (i > 1 && Number.isInteger(i)) ? i : 1
}
const updateFrequnce = PIN(updateFrequency)
const start = () => {
if (cu.manuallyPaused) cu.manuallyPaused = false
if (!cu.intervalId) {
getValueFromLocalStorage()
cu.intervalId = setInterval(() => {
cu.elapsedTime = new Date().getTime() - cu.start + cu.lastSavedElapsedTime
//t preserve real elapsed time.
if (!(cu.elapsedTime % updateFrequnce)) setElapsedTime(cu.elapsedTime)
if (maximumValue && cu.elapsedTime >= maximumValue * 1000) {
if (callback) callback()
cu.elapsedTime = 0
cu.manuallyPaused = true
pause()
}
localStorage.setItem(LocalStorageItemName, cu.elapsedTime.toString())
}, 1000)
}
}
const pause = () => {
cu.lastSavedElapsedTime = cu.elapsedTime
if (cu.intervalId) {
clearInterval(cu.intervalId)
cu.intervalId = null
}
}
const manuallyPause = () => {
cu.manuallyPaused = true
pause()
}
const resetTimer = () => {
cu.lastSavedElapsedTime = 0
cu.elapsedTime = 0
localStorage.setItem(LocalStorageItemName, "0")
cu.start = new Date().getTime()
setElapsedTime(0)
}
useEffect(() => {
getValueFromLocalStorage()
window.onblur = () => {
if (pauseOnNoFocus) pause()
}
window.onfocus = () => {
if (cu.manuallyPaused) return
if (pauseOnNoFocus) start()
}
start()
return () => {
if (cu.intervalId) {
clearInterval(cu.intervalId)
cu.intervalId = null
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [pauseOnNoFocus, LocalStorageItemName, maximumValue, callback, updateFrequency])
return [elapsedTime, start, manuallyPause, resetTimer]
}
export default usePersistantTimer
我实际上会重新思考当前计时器是如何实现的,以使其更加准确。但是,您可以利用窗口before unload
事件,并设置一个并行计时器,以每隔一段时间更新本地存储。也就是说,你需要小心过于频繁地设置为local storage
,因为如果你保存了大量的对象/数据,尤其是在较小的设备上,它会大大影响性能。这是因为对localstorage
的读写是同步操作,通常需要JSON.stringify
和JSON.parse
对其进行读写。关键的收获是你需要:
- 当计时器暂停和取消暂停时,用计时器数据更新localstorage
- 使用开始时间和结束时间来跟踪计时器,因为计数器不准确(React状态更新并非总是立即的(
- 以更新本地存储的间隔运行函数。间隔越低,就性能而言成本就越高
import React, { useState, useEffect } from "react";
import { unstable_batchedUpdates } from "react-dom";
import {useDispatch} from 'react-redux'
function addMinutes(date, minutes) {
return new Date(date.getTime() + minutes * 60000);
}
function addMillseconds(date, millseconds) {
return new Date(date.getTime() + millseconds);
}
export default function Timer() {
const currDate = new Date();
const [timeInactiveStart, setTimeInactiveStart] = useState(currDate);
const [inactiveTimerActive, setInactiveTimerActive] = useState(false);
const [timeStart, setTimeStart] = useState(currDate);
const [timeEnd, setTimeEnd] = useState(addMinutes(currDate, 10));
const timeRemaining = timeEnd.getTime() - timeStart.getTime();
const dispatch = useDispatch()
// Stop Counter when tab is switched
useEffect(() => {
window.onblur = function () {
//start parallel timer
const timeStartDate = new Date();
//we save here in case the user closes the page while away.
//that way we still know how much time they have left
const timerObj = {
startTime: timeStartDate,
endTime: timeEnd,
remainingTime: timeEnd.getTime() - timeStartDate.getTime(),
inactiveTimerActive: true,
};
localStorage.setItem("timerData".JSON.stringify(timerObj));
setTimeInactiveStart(timeStartDate);
//stop timer
setInactiveTimerActive(true);
};
window.onfocus = function () {
//end parallel timer
const timeInactiveEnd = new Date();
const timeElapsedInactive =
timeInactiveEnd.getTime() - timeInactiveStart.getTime();
//add time to intervals and now we can store them, so its not out of sync
const newEndTime = addMillseconds(timeEnd, timeElapsedInactive);
const newStartTime = addMillseconds(timeStart, timeElapsedInactive);
const timerObj = {
startTime: newStartTime.toString(),
endTime: newEndTime.toString(),
//we store this in case a user exists the page, we have a restarting point
remainingTime: newEndTime.getTime() - newStartTime.getTime(),
inactiveTimerActive: false,
};
unstable_batchedUpdates(() => {
localStorage.setItem("timerData", JSON.stringify(timerObj));
setTimeEnd(newEndTime);
setTimeStart(newStartTime);
//restart timer
setInactiveTimerActive(false);
});
};
window.onbeforeunload = function () {
//by nature this wont always occur, so
//if you need to keep the timer countdown with higher integrity,
// consider updating to local storage every minute or 5 minutes, depending on
// your use case. However, every second would be too frequent
const timerObj = {
startTime: timeStart.toString(),
endTime: timeEnd.toString(),
//we store this in case a user exists the page, we have a restarting point
remainingTime: timeEnd.getTime() - timeStart.getTime(),
inactiveTimerActive: inactiveTimerActive,
};
localStorage.setItem("timerData", JSON.stringify(timerObj));
};
});
//set a timer for a custom interval.
// To create checkpoints. However, the lower the time between intervals,
//the larger the performance hit, so don't update every second. In this example it updates every minute
useEffect(() => {
const updateStorage = setInterval(() => {
const timerObj = {
startTime: timeStart,
endTime: timeEnd,
inactiveTimerActive: inactiveTimerActive,
remainingTime: timeEnd.getTime() - timeStart.getTime(),
};
localStorage.setItem("timerData", JSON.stringify(timerObj));
}, 60000);
return () => clearInterval(updateStorage);
}, [timeEnd, timeStart, inactiveTimerActive]);
useEffect(() => {
const timer = setInterval(() => {
//we increment if these are correct
if (!inactiveTimerActive && timeStart.getTime() < timeEnd.getTime()) {
setTimeStart(new Date());
}
//clear local storage if timer has ended
else if (timeStart.getTime() > timeEnd.getTime())
localStorage.removeItem("timerData");
}, 1000);
return () => clearInterval(timer);
}, [inactiveTimerActive, timeStart, timeEnd]);
//on mount we fetch from localstorage our timer values
useEffect(() => {
const timerData = localStorage.getItem("timerData");
if (timerData) {
const data = JSON.parse(timerData);
const newDate = new Date();
unstable_batchedUpdates(() => {
setTimeStart(new Date());
//add time remaining since timer was inactive when tab was closed
setTimeEnd(addMillseconds(newDate, data.remainingTime));
});
}
}, []);
//here you can pass time remaining anywhere, etc.
//Passing CountDown Vlaue to the Quiz.js through Redux Toolkit
//dispatch(timerEnd(timer));
return (
<div className="App">
<div className="timer">
Time Remaining:{" "}
{timeRemaining > 0 ? Math.floor(timeRemaining / 1000) : 0} seconds
</div>
</div>
);
}