使用挂钩同步状态倒计时与本地存储的反应组件

react component using hooks sync state count down time with local storage


import React , {useState, useEffect} from 'react';

const Timer = () =>{
    const [timer, setTimer] = useState(120);
    
    const countTimer = () =>{
        if(timer <= 0){
             localStorage.clear("timer");
             console.log("timer less then 0")
             return;
        } else {
            console.log("greater then 0")
            setTimer(timer -1) ;
            localStorage.setItem("timer",timer);
            setTimeout(countTimer(),1000);
        }
    }
    
    
    useEffect(()=>{
        if(localStorage.getItem("timer")){
            setTimer(localStorage.getItem("timer"));
        } else {
            setTimer(120);
        }
        if(timer){
            setTimeout(countTimer(),1000);
        }
    },[timer])
    return (
        <div align="center">
            Timer :{timer}  
        </div>
    )
}

export default Timer

我的目标是在 React 中实现倒数计时器,它将与本地存储同步并跨浏览器选项卡与显示计数器的页面同步, 想同步状态定时器和本地存储定时器,把它们放在 setTimeout 里面使用定时器改变更新状态定时器和更新本地存储的效果,拜托!帮帮我,谢谢

您的代码存在一些问题。

第一个问题是 setTimeout 接受回调函数作为第一个参数。它将在作为第二个参数提供的时间之后执行。但不是传递函数 countTimer 作为回调,而是传递它的执行结果。

if(timer){
    setTimeout(countTimer(),1000); // here you're passing as the callback 
                                   // the result of execution of countTimer
                                   // namely `undefined` value
}

另一个问题是您两次调用 setTimeout。每次 timer 变量更改时将执行的内部 useEffect 挂钩以及每次执行时内部 countTimer 函数。

还有一个问题。每次 timer 变量发生变化时,您都会从 localStorage 读取 timer 键并再次设置 setTimer。当 stateuseEffect 改变时,它会导致 React 变为 schedule another render。而且您的代码将触发几乎无休止的循环。这将不断重新渲染组件,直到时间用完。

还有一个问题。如果您的组件在超时仍在运行时被卸载。挂起的功能最终将被执行。它会将卸载前的 timer 值写入 localStorage。如果您再次挂载此组件,执行时的挂起超时可能会覆盖新创建的值。

清除任何挂起的超时或对卸载的其他影响是一个好习惯。因此,如果 setTimeout 仍在运行,您最好清除它。为此,您必须 return cleanup 作为 useEffect 钩子中的 return 值。

总结:

  • 将回调函数传递给 setTimeout,而不是它执行的结果
  • 不要创建多个挂起超时
  • 不要在 useEffect
  • 中触发无休止的重新渲染
  • 组件卸载时的清理挂起超时

总而言之,它应该是这样的:

import * as React from 'react'

const Timer = () => {
    const initialTimer = localStorage.getItem("timer") ?? 120;
    const timeoutId = React.useRef(null);
    const [timer, setTimer] = React.useState(initialTimer);

    const countTimer = React.useCallback(() => {
        if (timer <= 0) {
            localStorage.clear("timer");
        } else {
            setTimer(timer - 1);
            localStorage.setItem("timer", timer);
        }
    }, [timer]);

    React.useEffect(() => {
        timeoutId.current = window.setTimeout(countTimer, 1000);
        // cleanup function
        return () => window.clearTimeout(timeoutId.current);
    }, [timer, countTimer]);

    return <div align="center">Timer :{timer}</div>;
}

export default Timer

codesandbox link