所以我有一个带有倒计时器的反应模式。计时器似乎不适用于 Chrome,但适用于 Firefox。在 Chrome 上,计时器开始倒计时,但在滴答大约 20 秒时会减少滴答声。
这是模态和计时器的当前实现。
import React, {useState, useEffect } from 'react';
import BaseModal from './Index';
export default function OtpModal(props: Props) {
const [timeLeft, setTimeLeft] = useState<number>(120);
useEffect(() => {
const intervalId = setInterval(() => {
console.log('tick', timeLeft);
setTimeLeft(timeLeft - 1);
}, 1000);
return () => clearInterval(intervalId);
}, [timeLeft]);
return (
<BaseModal>
<div>
OTP timeout in{' '}
<span style={{fontWeight: '500'}}>
{Math.floor(timeLeft / 60)}:
{(timeLeft % 60).toString().padStart(2, '0')}
</span>
</div>
</BaseModal>
)
}
您正在运行 useEffect 并每秒设置一个新计时器。
如果存在计时器限制,可能会导致问题。
您只能设置一个计时器并运行一次 useEffect,如下所示:
useEffect(() => {
const intervalId = setInterval(() => {
setTimeLeft((prev) => (prev > 0 ? prev - 1 : 0));
}, 1000);
return () => {
clearInterval(intervalId);
};
}, []);
当我们运行
clearInterval
和 setInterval
时,它们的时间会发生变化。如果我们过于频繁地重新渲染和重新应用效果,则该间隔永远没有机会触发!
我建议学习这篇博文,了解
setInterval
和 React hooks 的问题。该博客文章建议创建一个自定义钩子(useInterval
),如下所示:
import React, { useState, useEffect, useRef } from 'react';
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]);
}
用法
useInterval(() => {
setTimeLeft((prev) => (prev > 0 ? prev - 1 : 0));
}, delay);
创建一个名为
useTimer
的钩子来处理倒计时会更容易。这样,每个<Timer>
只需要知道它将渲染的时间。
const { useCallback, useEffect, useState } = React;
const useTimer = ({ seconds, refreshRate = 50 }) => {
const [timeLeft, setTimeLeft] = useState(seconds);
const [intervalId, setIntervalId] = useState(null);
useEffect(() => {
const endTime = Date.now() + (seconds * 1e3);
setIntervalId((currentIntervalId) => {
if (currentIntervalId) {
clearInterval(currentIntervalId);
}
const internalIntervalId = setInterval(() => {
const diff = Math.floor((endTime - Date.now()) / 1e3);
setTimeLeft(diff);
if (diff < 1) {
clearInterval(internalIntervalId);
}
}, refreshRate)
return internalIntervalId;
});
}, [seconds, refreshRate]);
const cancel = useCallback(() => {
clearInterval(intervalId);
}, [intervalId]);
return { timeLeft, cancel };
}
const Timer = ({ seconds }) => {
const { timeLeft, cancel } = useTimer({ seconds });
// Cancel all timers after 30 seconds...
useEffect(() => {
if (cancel) {
setTimeout(() => {
cancel();
}, 3e4); /* 30 seconds */
}
}, [cancel]);
return (
<div>
{Math.floor(timeLeft / 60)}:
{(timeLeft % 60).toString().padStart(2, '0')}
</div>
);
};
const App = () => {
return (
<div>
<Timer seconds={120} />
<Timer seconds={60} />
<Timer seconds={30} />
<Timer seconds={10} />
</div>
);
};
ReactDOM
.createRoot(document.getElementById("root"))
.render(<App />);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.development.js"></script>