为啥间隔回调属于第一次渲染不能在每次间隔触发时向 React 发送更新指令(计数 +1)?

Posted

技术标签:

【中文标题】为啥间隔回调属于第一次渲染不能在每次间隔触发时向 React 发送更新指令(计数 +1)?【英文标题】:Why the interval callbacks belongs to first render is not capable of sending the update instruction(count +1) to React everytime the interval fires?为什么间隔回调属于第一次渲染不能在每次间隔触发时向 React 发送更新指令(计数 +1)? 【发布时间】:2020-09-04 09:32:35 【问题描述】:

我是 React 的初学者,遇到了一些问题。如下所示,我们可以看到对于下面的 Counter Functional 组件只能调用一次 useEffect(因为在依赖数组中我没有指定计数状态)。 但是为什么间隔回调属于第一次渲染不能在每次间隔触发时发送更新指令呢?

import React,  useState, useEffect  from "react";
import ReactDOM from "react-dom";

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

   useEffect(() => 
     const id = setInterval(() => 
       setCount(count + 1);
     , 1000);

     return () => clearInterval(id);
  , []);

  return <h1>count</h1>;


const rootElement = document.getElementById("root");
ReactDOM.render(<Counter />, rootElement);

【问题讨论】:

【参考方案1】:

您将一个空数组传递给useEffect,这意味着,由于在重新渲染期间没有任何数组值发生变化,因此useEffect 调用仅在初始渲染时调用一次。

count 放入数组中,以便useEffect 回调仅在count 更改时运行 - 或者,完全删除第二个参数,以便回调在每次渲染时运行。

还要注意,由于每次运行 setInterval 回调都会导致重新渲染,因此使用 setTimeout 会更有意义(它不会影响功能,但它会使代码更直观可以理解):

function Counter() 
  const [count, setCount] = React.useState(0);
  React.useEffect(() => 
    const id = setTimeout(() => 
      setCount(count + 1);
    , 1000);
    return () => clearTimeout(id);
  , [count]);

  return <h1>count</h1>;


const rootElement = document.getElementById("root");
ReactDOM.render(<Counter />, rootElement);
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>

<div id="root"></div>

【讨论】:

我喜欢你的回答,这是正确的,但我只是好奇,为什么 setInterval 不起作用呢?假设在 ComponentDidMount 上您做了与问题 (setInterval) 类似的事情,那么即使您不再进入该代码块,也不应该每秒调用一次间隔吗? setInterval 确实工作,这很奇怪,因为你只希望它的回调运行一次最多,所以setTimeout更多合适的。如果useEffect 回调返回一个值,则在需要清理组件时运行该回调(例如重新渲染),因此间隔被清除。 (即使没有清除间隔,代码仍然可以工作,只是不够优雅,因为产生了多个间隔来更改断开连接的 DOM 元素) 我还要补充一点,如果你控制台记录count 中的countsetInterval() 你会注意到它总是0。我不是专家,但我想说的是,当setInterval() 注册时,它将采用当时的count 的值。您所期望的是count 会在每个时间间隔内发生变化,但事实并非如此。这个答案是正确和干净的,我只是想提供更多的见解。 @CertainPerformance,我有一个问题,正如我上面提到的,我没有在依赖数组中提到计数状态,因此 useEffect 将被调用一次。但是第一次渲染的间隔仍然存在它将为每个 1 秒的时间间隔调用 setCount(count +1)。那么为什么间隔不会向 React 发送更新指令。例如:- setCount(0+1) 用于第一次渲染,现在 setCount(1+1) 调用下一秒,setCount(2+1) 调用下一秒。如果它实际上没有发生,那么第一次渲染的间隔调用会发生什么。 @Abhijeet 从useEffect 回调返回的函数在组件被清理时运行,为重新渲染做准备,就像我上面评论的那样。因此,一旦间隔回调运行,setCount 就会被调用,它将渲染排队。当组件被卸载时,将调用清除间隔的回调。然后使用更新后的1count 渲染一个新的 Counter 组件。所以它根本不是一个真正的间隔,因为传递给它的给定回调只运行一次。

以上是关于为啥间隔回调属于第一次渲染不能在每次间隔触发时向 React 发送更新指令(计数 +1)?的主要内容,如果未能解决你的问题,请参考以下文章

js 如何控制文本域输入内容在一定间隔时间段才触发事件查询相关数据

新兴的API(javascript)

JS防抖与节流

间隔计时器未在指定时间间隔触发信号

深入理解JS防抖与节流

C# Timer 最简单形象的教程