为啥 useEffect 没有被触发?

Posted

技术标签:

【中文标题】为啥 useEffect 没有被触发?【英文标题】:Why is useEffect not being triggered?为什么 useEffect 没有被触发? 【发布时间】:2021-02-16 03:57:55 【问题描述】:

我有一个应该是运行时钟的功能组件:

import React,useState,useEffect from 'react';
import 'materialize-css/dist/css/materialize.min.css';
import  parseTime  from '../../Utils/utils'

const MainClock = (props) => 
    const [timeString, setTimeString] = useState(parseTime(new Date(), true));
    function tick()
        console.log("TICK:" + timeString)
        setTimeString(parseTime(new Date(), true));
    ;

    useEffect(()=>console.log("rendered!");setTimeout(tick,500);,[timeString]);
    return (
        <div>
            <h5 className="center-align mainclock">timeString</h5>
        </div>        
    );

 
export default MainClock;

但由于某种原因,它只被渲染了两次,控制台输出是:

rendered!
TICK:14:56:21
rendered!
TICK:14:56:22

为什么第二次渲染后没有调用useeffect?

欢迎任何帮助!

编辑:如果有帮助,这是parseTime

const parseTime = (timeDate, withSeconds=false) =>
    let time = timeDate.getHours()<10 ? `0$timeDate.getHours()`:`$timeDate.getHours()`;
    time+=":";
    time+= timeDate.getMinutes()<10 ? `0$timeDate.getMinutes()`:`$timeDate.getMinutes()`;
    if(withSeconds)
        time+=":";
        time+=timeDate.getSeconds()<10 ? `0$timeDate.getSeconds()`:`$timeDate.getSeconds()`;
    
    return time;

【问题讨论】:

这里有什么问题吗?我希望“渲染!TICK:...”将每 500 毫秒连续显示一次。 @WilliamWang 这就是问题所在。它没有连续显示“rendered!TICK: ...”,但只显示了两次。 我检查过,它工作正常。 是的,没有 parseTime,我可以看到连续打印 啊,我发现了错误,只是尝试更新setTimeout间隔而不是500。 【参考方案1】:

问题是使用setTimeout 和使用低延迟,即500ms 用于超时。如果你记录parseTime的返回值,你会注意到在两次调用之间,它返回相同的时间字符串,所以状态永远不会更新,导致组件永远不会重新渲染,因此useEffect永远不会再次执行设置另一个setTimeout

解决方案

增加超时延迟或检查parseTime函数的返回值,如果与状态相同,再次调用该函数。

此外,这里使用setInterval 代替setTimeout 更合适,因为setInterval 只需要调用一次,它会重复调用tick 函数,直到取消间隔。如果您使用setTimeout,那么您将需要一次又一次地调用setTimeout 来安排新的tick 函数调用。

【讨论】:

不会使用setInterval 在每次渲染后创建多个堆叠间隔?如果 tick 正在更新依赖数组中已经存在的变量,为什么我需要将 tick 添加到依赖数组? 我认为 setTimeout 是正确的而不是 setInterval 因为 setInterval 总是重复调用tick函数。 @MaorMagori 分享parseTime 的代码,我将添加更多解释为什么会出现问题以及如何解决它或在codesandbox 上分享一个可重现的示例。 @Yousaf 已添加。是 parseTime 导致问题的原因吗?【参考方案2】:

正如我上面指出的,这是短 setTimeout 计时值的问题 - 500ms。 要使其工作,您需要使用setInterval

const MainClock = (props) => 
    const [timeString, setTimeString] = useState(parseTime(new Date(), true));
    function tick()
        console.log("TICK:" + timeString)
        setTimeString(parseTime(new Date(), true));
    ;

    useEffect(() => 
      setInterval(tick, 500);
    , []);
    useEffect(()=>console.log("rendered!");,[timeString]);
    return (
        <div>
            <h5 className="center-align mainclock">timeString</h5>
        </div>        
    );

【讨论】:

你错过了useEffect钩子的依赖数组。您只希望useEffect 只运行一次,还应该使用清理功能取消setInterval

以上是关于为啥 useEffect 没有被触发?的主要内容,如果未能解决你的问题,请参考以下文章

React 中带有 useEffect 的异步上下文

useEffect 钩子没有被 jest.spyOn 嘲笑

如何在反应中只运行一次功能

如何取消firebase的useEffect订阅

渲染组件时不触发 useEffect

状态在 useEffect 中没有更新,为啥?