setInterval обновляет приложение вместо обновления состояния - PullRequest
0 голосов
/ 21 марта 2020

Я кодирую обратный отсчет с Expo. Я использую functional components, поэтому мои state обрабатываются через useState крючок React.

let [state, setState] = useState({
    secondsLeft: 25,
    started: false,
});

Если я нажимаю Button, он запускает эту функцию:

let onPressHandler = (): void => {
    if(!state.started) {
        setState({...state, started: true});
        setInterval(()=> {
            setState({...state, secondsLeft: state.secondsLeft - 1});
            console.log(state.secondsLeft);
        }, 1000);
    }
}

Проблема в том, что каждые 1000 мс Экспо обновляет приложение вместо обновления состояния.

enter image description here

Можете ли вы помочь мне пожалуйста?

1 Ответ

0 голосов
/ 21 марта 2020

Обновляет состояние, но для этого использует устаревшее состояние. Переменная state в вашем обратном вызове setInterval никогда не изменится после запуска интервала.

Вместо этого используйте форму установки функции обновления состояния, чтобы вы всегда работали с текущим состоянием на тот момент :

let onPressHandler = (): void => {
    if(!state.started) {
        setState({...state, started: true});
        setInterval(()=> {
            setState(currentState => {
                const newState = {...currentState, secondsLeft: currentState.secondsLeft - 1};
                console.log(newState.secondsLeft);
                return newState;
            });
        }, 1000);
    }
};

Это более кратко без console.log:

let onPressHandler = (): void => {
    if(!state.started) {
        setState({...state, started: true});
        setInterval(()=> {
            setState(currentState => {...currentState, secondsLeft: currentState.secondsLeft - 1});
        }, 1000);
    }
};

На отдельном примечании: если у вас есть элементы состояния, которые вы обновляете независимо друг от друга, лучше всего практика заключается в использовании отдельных переменных состояния для них. Кроме того, поскольку они постоянны в вашей функции, лучше всего объявить их как const. Например:

const [secondsLeft, setSecondsLeft] = useState(25);
const [started, setStarted] = useState(false);

// ...
let onPressHandler = (): void => {
    if(!started) {
        setStarted(true);
        setInterval(()=> {
            setSecondsLeft(seconds => seconds - 1);
        }, 1000);
    }
};

Кроме того, поскольку вы не можете полагаться на setInterval, чтобы быть точным, я предлагаю сохранить ваше время остановки («сейчас» плюс 25 секунд) и пересчитать, сколько секунд оставляется каждый раз:

let onPressHandler = (): void => {
    const stopTime = Date.now() + (DURATION * 1000);
    setStarted(true);
    setSecondsLeft(DURATION);
    const timer = setInterval(()=> {
        const left = Math.round((stopTime - Date.now()) / 1000);
        if (left <= 0) {
            clearInterval(timer);
            setStarted(false);
        } else {
            setSecondsLeft(left);
        }
    }, 1000);
};

Пример Live (с логикой c для остановки):

const {useState} = React;

const Example = () => {
    const DURATION = 25; // seconds
    const [started, setStarted] = useState(false);
    const [secondsLeft, setSecondsLeft] = useState(0);

    if (started) {
        return <div>Seconds left: {secondsLeft}</div>;
    }

    let onPressHandler = ()/*: void*/ => {
        const stopTime = Date.now() + (DURATION * 1000);
        setStarted(true);
        setSecondsLeft(DURATION);
        const timer = setInterval(()=> {
            const left = Math.round((stopTime - Date.now()) / 1000);
            if (left <= 0) {
                clearInterval(timer);
                setStarted(false);
            } else {
                setSecondsLeft(left);
            }
        }, 1000);
    };
    return (
        <input
            type="button"
            onClick={onPressHandler}
            value="Start"
        />
    );
};

ReactDOM.render(<Example />, document.getElementById("root"));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.12.0/umd/react-dom.production.min.js"></script>
...