Перехватчики setInterval и React дают неожиданные результаты - PullRequest
0 голосов
/ 06 апреля 2020

У меня есть следующий компонент, определенный в моём приложении с использованием create-реагировать:

import React, { useState } from 'react';

const Play = props => {
  const [currentSecond, setCurrentSecond] = useState(1);
  let timer;
  const setTimer = () => {
    timer = setInterval(() => {
      if (currentSecond < props.secondsPerRep) {
        setCurrentSecond(() => currentSecond + 1);
      }
    }, 1000);
  }
  setTimer();
  return (
    <div>
      <div>
        <p>{currentSecond}</p>
      </div>

    </div>
  );
}

export default Play;

И currentSecond обновляется каждую секунду, пока не достигнет props.secondsPerRep, однако, если я пытаюсь запустить setInterval из обработчика кликов:

import React, { useState } from 'react';

const Play = props => {
  const [currentSecond, setCurrentSecond] = useState(1);
  let timer;
  const setTimer = () => {
    timer = setInterval(() => {
      if (currentSecond < props.secondsPerRep) {
        setCurrentSecond(() => currentSecond + 1);
      }
    }, 1000);
  }

  return (
    <div>
      <div>
        <button onClick={setTimer}>Start</button>
        <p>{currentSecond}</p>
      </div>

    </div>
  );
}

export default Play;

Тогда currentSecond в обратном вызове setInterval всегда возвращается к начальному значению, то есть 1.

Любая помощь приветствуется!

Ответы [ 4 ]

1 голос
/ 06 апреля 2020

https://overreacted.io/making-setinterval-declarative-with-react-hooks/ - отличный пост, на который можно посмотреть и узнать больше о том, что происходит. Хук React useState плохо работает с setInterval, поскольку он получает значение хука только при первом рендеринге, а затем повторно использует это значение, а не обновленное значение из будущих рендеров.

В этом посте Дэн Абрамов приводит пример пользовательского хука, чтобы заставить интервалы работать в React, которые вы могли бы использовать. Это сделало бы ваш код более похожим на это. Обратите внимание, что мы должны изменить способ запуска таймера для запуска с другой переменной состояния.

const Play = props => {
  const [currentSecond, setCurrentSecond] = React.useState(1);
  const [isRunning, setIsRunning] = React.useState(false);
  useInterval(() => {
    if (currentSecond < props.secondsPerRep) {
      setCurrentSecond(currentSecond + 1);
    }
  }, isRunning ? 1000 : null);

  return (
    <div>
      <div>
        <button onClick={() => setIsRunning(true)}>Start</button>
        <p>{currentSecond}</p>
      </div>
    </div>
  );
}

Я пошел дальше и собрал примерный кодовый код для вашего варианта использования, если вы хотите поэкспериментировать с ним и увидеть как это работает.

https://codepen.io/BastionTheDev/pen/XWbvboX

1 голос
/ 06 апреля 2020

Ваша проблема в этой строке setCurrentSecond(() => currentSecond + 1);, потому что вы звоните setTimer только один раз, ваш интервал всегда будет закрыт над начальным состоянием, где currentSecond равно 1.

К счастью, вы можете легко исправить это происходит путем доступа к текущему текущему состоянию через аргументы в функции, которую вы передаете setCurrentSecond, например setCurrentSecond(actualCurrentSecond => actualCurrentSecond + 1)

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

Я бы порекомендовал ознакомиться с этим сообщением в блоге, потому что он будет отвечать на любые вопросы. у вас есть вопросы об интервалах + крючки: https://overreacted.io/making-setinterval-declarative-with-react-hooks/

0 голосов
/ 06 апреля 2020

Попробуй это. Проблема заключалась в том, что вы не используете состояние, полученное функцией setCurrentSecond, а функция setInterval не видит изменения состояния.

const Play = props => {
  const [currentSecond, setCurrentSecond] = useState(1);
  const [timer, setTimer] = useState();
  const onClick = () => {
    setTimer(setInterval(() => {
      setCurrentSecond((state) => {
        if (state < props.secondsPerRep) {
          return state + 1;
        }
        return state;
      });
    }, 1000));
  }

  return (
    <div>
      <div>
        <button onClick={onClick} disabled={timer}>Start</button>
        <p>{currentSecond}</p>
      </div>

    </div>
  );
}
0 голосов
/ 06 апреля 2020

Это потому, что ваш код закрывает значение currentSecond от рендера до того, как вы нажали кнопку. То есть javascript не знает о повторных рендерах и хуках. Вы хотите настроить это немного по-другому.

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

const Play = ({ secondsPerRep }) => {
  const secondsPassed = useRef(1)
  const [currentSecond, setCurrentSecond] = useState(1);
  const [timerStarted, setTimerStarted] = useState(false)

  useEffect(() => {
    let timer;

    if(timerStarted) {
      timer = setInterval(() => {
        if (secondsPassed.current < secondsPerRep) {
          secondsPassed.current =+ 1

          setCurrentSecond(secondsPassed.current)
        }
      }, 1000);
    }

    return () => void clearInterval(timer)
  }, [timerStarted])

  return (
    <div>
      <div>
        <button onClick={() => setTimerStarted(!timerStarted)}>
          {timerStarted ? Stop : Start}
        </button>
        <p>{currentSecond}</p>
      </div>

    </div>
  );
}

export default Play;

Зачем вам нужен ref и состояние? Если у вас будет только состояние, метод очистки эффекта будет запускаться каждый раз, когда вы обновляете свое состояние. Следовательно, вы не хотите, чтобы ваше состояние влияло на ваш эффект. Вы можете достичь этого, используя ref для подсчета секунд. Изменения в ссылке не приведут к запуску эффекта или его очистке.

Однако вам также необходимо состояние, потому что вы хотите, чтобы ваш компонент повторно отображал после выполнения вашего условия. Но поскольку методы средства обновления для состояния (т. Е. setCurrentSecond) являются постоянными, они также не влияют на эффект.

И последнее, но не менее важное: я отделился, установив интервал из вашей логики подсчета c , Я сделал это с дополнительным состоянием, которое переключается между true и false. Поэтому, когда вы нажимаете кнопку, состояние переключается на true, эффект запускается и все настраивается. Если ваши компоненты отключены, или вы остановили таймер, или secondsPerRep реквизит изменяет старый интервал, очищается и устанавливается новый.

Надеюсь, это поможет!

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...