为什么useEffect没有被触发?

4
我有一个功能组件,应该是一个运行中的时钟:
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;
}

只需删除parseTime和try。 - wangdev87
@WilliamWang parseTime 函数将一个给定的日期对象格式化为 hh:mm:ss 字符串。如果你说它对你来说正常工作,我真的不确定发生了什么。你的控制台输出是否持续打印新的 "TICK!..."? - Maor Magori
1
是的,没有parseTime,我可以看到连续的打印。 - wangdev87
没有问题。 - wangdev87
1
啊,我找到错误了,只需要尝试将setTimeout间隔更新为非500即可。 - wangdev87
显示剩余6条评论
2个回答

4
问题在于使用了 setTimeout 并且延迟很低,即 500ms。如果记录 parseTime 的返回值,你会发现在两次调用之间它返回相同的时间字符串,因此状态永远不会更新,导致组件永远不会重新渲染,useEffect 因此也不会再次执行以设置另一个 setTimeout
解决方法是增加超时延迟或检查 parseTime 函数的返回值,如果与状态中的值相同,则再次调用此函数。
此外,在这里更合适的是使用 setInterval 而不是 setTimeout,因为 setInterval 只需要被调用一次,并且会重复调用 tick 函数直到取消间隔为止。如果使用 setTimeout,则需要一遍又一遍地调用 setTimeout 来安排新的 tick 函数调用。

使用 setInterval 不会在每次渲染后创建多个堆叠的间隔吗?如果 tick 已经更新了依赖数组中已经存在的变量,为什么还需要将 tick 添加到依赖数组中呢? - Maor Magori
我认为setTimeout比setInterval更合适,因为setInterval总是会重复调用tick函数。 - wangdev87
@MaorMagori 分享 parseTime 的代码,我会添加更多解释为什么问题会出现以及如何解决它,或者在 codesandbox 上分享一个可重现的示例。 - Yousaf
@Yousaf 已添加。是 parseTime 导致了问题吗? - Maor Magori

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

1
您错过了 useEffect 钩子的依赖数组。您只想让 useEffect 运行一次,并且还应该使用清理函数来取消 setInterval - Yousaf

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接