(React 渲染)SetInterval 只会变得更快且不可预测

(React rendering) SetInterval just keeps going faster and unpredictable

我正在尝试实现一个每秒连续计数的简单计时器。我正在使用反应来呈现结果。该代码只有几行,并且从我一直在阅读的内容来看是有意义的。它在前 6 秒内正确添加和呈现;但是,它只是在 7 号或 8 号之后才开始显示随机数,或者计时器变得错误并随机更新每一秒。我的代码如下,我做错了吗?谢谢!

import React, {useState} from 'react';

function App() {

  const [count, setCount] = useState(0); 

  setInterval(()=>{setCount(count + 1)}, 1000)

  return (
    <div className="welcome">
      {count}
    </div>
  );
}

export default App;

setInterval 调用应该放在 useEffect 钩子中,清理也应该完成:

import React, { useState, useEffect, useRef } from "react";

function App() {
  const [count, setCount] = useState(0);
  const ref = useRef();

  useEffect(() => {
    ref.current = setInterval(() => {
      setCount(count + 1);
    }, 1000);
    return () => {
      clearInterval(ref.current);
    };
  }, [count]);

  return <div className="welcome">{count}</div>;
}

export default App;

那是因为每次组件重新渲染时它都会启动一个新的 用这个代替

import React, {useState} from 'react';

function App() {

  const [count, setCount] = useState(0); 
  const timer = useRef(null);


useEffect(() => {
  timer.current = setInterval(()=>{setCount(count + 1)}, 1000);
  return () => {
    if(timer.current !== null) clearInterval(timer.current);
  };
}, []);

  return (
    <div className="welcome">
      {count}
    </div>
  );
}

export default App;


您的代码和其他答案一样存在一些问题。它们可以概括为三点:

  1. 如果你想使用 Interval 或 Timeout(或任何其他事件侦听器),你需要在 useEffect 中执行此操作,这样你就不会在每个中添加新的侦听器渲染。

  2. 如果使用 useEffect,则需要清理 Interval/Timeout(或任何其他侦听器)以避免内存泄漏。

  3. 我们可以通过使用 setCount(oldCount => oldCount + 1) 来避免在 dependencies 数组中使用 count 变量。引用 :

If the new state is computed using the previous state, you can pass a function to setState. The function will receive the previous value, and return an updated value.

如果您通过使用 count 作为挂钩依赖项来忽略第 3 步,您将在每个渲染中创建并清理一个间隔 - 这是不必要的。
如果您决定只使用 setCount(count + 1) 而不将其添加到依赖项数组,那么您使用的钩子是错误的,count 将始终具有相同的值(初始值)。

我在下面的示例中添加了一个强制重新渲染的按钮,以显示计数继续按预期进行。

function App() {
  const [count, setCount] = React.useState(0);
  const [forced, setForced] = React.useState(0);

  React.useEffect(() => {
    // Store the interval id in a const, so you can cleanup later
    const intervalId = setInterval(() => {
      setCount(oldCount => oldCount + 1);
    }, 1000);
    
    return () => {
      // Since useEffect dependency array is empty, this will be called only on unmount
      clearInterval(intervalId);
    };
  }, []);
  
  function forceRerender() {
    setForced(oldForced => oldForced + 1);
  }

  return (
    <div>
      <p>{count} seconds</p>
      <p>Forced re-renders: {forced}</p>
      <button onClick={forceRerender}>Force a re-render</button>
    </div>
  );
}

ReactDOM.render(<App />, document.querySelector('#app'));
<div id="app"></div>
<script crossorigin src="https://unpkg.com/react@17/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.production.min.js"></script>