useEffect 不会更新状态

useEffect won't update state

我正在创建一个简单的番茄应用程序,但卡在了计时器更新逻辑上。

我正在使用 useEffect 更新计时器,但由于某种原因,一秒钟后,计时器停止减少。它似乎没有得到更新,因为控制台日志不断打印初始值。

这是一个可测试的 stackbliz

这是完整的组件:

import './Tomato.css';
import { useState, useEffect } from 'react';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faPlay, faPause, faArrowsRotate, faArrowDown, faArrowUp } from '@fortawesome/free-solid-svg-icons'

function Tomato() {
    
    const [settings, setSettings] = useState({session: 25, break: 5});
    const [timer, setTimer] = useState({minutes: '25', seconds: '00', active: false, isBreak: false});
    

    useEffect(() => {
        let timerInterval;

        if(timer.active) {
            timerInterval = setInterval(() => {
                let seconds = parseInt(timer.seconds);
                let minutes = parseInt(timer.minutes);
                console.log(seconds, ':',minutes)
                if(seconds === 0 && minutes === 0) {
                //  setTimer( {
                //      minutes: settings.isBreak ? settings.session : settings.break, 
                //      seconds: '00', 
                //      active: true, 
                //      isBreak: !settings.isBreak
                //  })
                //  // TODO BIP
                } else if(seconds === 0) {
                    const newTimer = {
                        ...timer,
                        minutes: timer.minutes-1,
                        seconds: '59',
                    }
                    setTimer( newTimer)
                } else {
                    console.log('in')
                    seconds = seconds - 1;
                    // this is not working as expected
                    setTimer( {
                        ...timer,
                        seconds: seconds < 10 ? '0'+seconds : seconds+''
                    })
                }
            }, 1000);
        } else {
            clearInterval(timerInterval);
        }

        return function cleanup() {
            clearInterval(timerInterval);
        };
    }, [timer.active]);


    const toggleTimer = (value) => {
        setTimer({ ...timer, active: value})
    }

    const refreshTimer = () => {
        setTimer({ 
            minutes: settings.session,
            seconds: '00', 
            active: false,
            isBreak: false
        })
    }

    const editSettings =  (type, val) => {
        if(timer.active) return
        const newSettings = { ...settings }
        newSettings[type] +=val;
        if(newSettings[type] > 60) {
            newSettings[type] = 60
        }
        if(newSettings[type] < 1) {
            newSettings[type] = 1
        }
        setSettings(newSettings)
    }

    return (
        <div className="Tomato">
            
            <h2 className="title">Session</h2>
            <div className="session-container">
                <div className='session-value'>{timer.minutes}:{timer.seconds}</div>
                <div className='controls-container'>
                    <span className='icon-container'>
                        <FontAwesomeIcon onClick={()=>{ toggleTimer(true)}} icon={faPlay} />
                    </span>
                    <span className='icon-container'>
                        <FontAwesomeIcon onClick={()=>{ toggleTimer(false)}} icon={faPause} />
                    </span>
                    <span className='icon-container'>
                        <FontAwesomeIcon onClick={()=>{ refreshTimer()}} icon={faArrowsRotate} />
                    </span>
                </div>
            </div>

            <h2 className="title">Session Length</h2>
            <div className="setting-container">
                <span className='icon-container' onClick={()=> editSettings('session', -1)}>
                    <FontAwesomeIcon icon={faArrowDown} />
                </span>
                <div className="setting-value">{settings.session}</div>
                <span className='icon-container' onClick={()=> editSettings('session', 1)}>
                    <FontAwesomeIcon icon={faArrowUp} />
                </span>
            </div>
            
            <h2 className="title">Break Length</h2>
            <div className="setting-container">
                <span className='icon-container' onClick={()=> editSettings('break', -1)}>
                    <FontAwesomeIcon icon={faArrowDown} />
                </span>
                <div className="setting-value">{settings.break}</div>
                <span className='icon-container' onClick={()=> editSettings('break', +1)}>
                    <FontAwesomeIcon icon={faArrowUp} />
                </span>
            </div>
            
        </div>
        );
    }
    
    export default Tomato;
    

这是典型的陈旧状态问题,timer 间隔内是陈旧的,因为您没有将其放入效果部门。只需将 deps 数组中的 timer.active 更改为 timer

...
  return function cleanup() {
            clearInterval(timerInterval);
        };
    }, [timer]);

如果您想了解有关此特定问题的更多信息: https://overreacted.io/making-setinterval-declarative-with-react-hooks/