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

时间:2021-06-25 16:37:15

标签: javascript react-hooks local-storage event-listener


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

我的目标是在反应中实现倒数计时器,它将与本地存储和跨浏览器选项卡同步,页面显示计数器, 想要同步状态定时器和本地存储定时器,把它们放在 useEffect 定时器更改状态更新定时器和更新本地存储内部 setTimeout ,拜托!帮帮我,谢谢

1 个答案:

答案 0 :(得分:0)

您的代码存在一些问题。

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

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

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

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

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

这是清除任何挂起的超时或其他对卸载的影响的好习惯。所以你最好清理 setTimeout 如果它仍然运行。为此,您必须返回 cleanup 函数作为 useEffect 钩子的返回值。

总结:

  • 将回调函数传递给 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