SetInterval 在 ReactJS 和 Chrome 中无法正常工作

问题描述 投票:0回答:3

所以我有一个带有倒计时器的反应模式。计时器似乎不适用于 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>
  )
}
javascript reactjs react-hooks settimeout setinterval
3个回答
1
投票

您正在运行 useEffect 并每秒设置一个新计时器。

如果存在计时器限制,可能会导致问题。

您只能设置一个计时器并运行一次 useEffect,如下所示:

useEffect(() => {
    const intervalId = setInterval(() => {
      setTimeLeft((prev) => (prev > 0 ? prev - 1 : 0));
    }, 1000);

    return () => {
      clearInterval(intervalId);
    };
  }, []);

0
投票

当我们运行

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);

0
投票

创建一个名为

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>

© www.soinside.com 2019 - 2024. All rights reserved.