Как я могу очистить Интервал по нажатию с помощью React Hooks?

Я пытаюсь реорганизовать мой код, чтобы реагировать на хуки, но я не уверен, правильно ли я это делаю. Я попытался скопировать и вставить свой код setInterval /setTimout в ловушки, но он не работал должным образом. Попробовав разные вещи, я смог заставить его работать, но я не уверен, что это лучший способ сделать это.

Я знаю, что могу использовать useEffect для очистки интервала при отключении, но я хочу очистить его перед отключением.

Является ли следующая хорошая практика, и если нет, то как лучше очистить setInterval /setTimout перед демонтажем?

Спасибо,

useTimeout

import { useState, useEffect } from 'react';

let timer = null;

const useTimeout = () => {
    const [count, setCount] = useState(0);
    const [timerOn, setTimerOn] = useState(false);

    useEffect(() => {
        if (timerOn) {
            console.log("timerOn ", timerOn);

            timer = setInterval(() => {
                setCount((prev) => prev + 1)
            }, 1000);

        } else {
            console.log("timerOn ", timerOn);
            clearInterval(timer);
            setCount(0);
        }
    return () => {
        clearInterval(timer);
    }
    }, [timerOn])

    return [count, setCount, setTimerOn];
}

export default useTimeout;

Составная часть

import React from 'react';
import useTimeout from './useTimeout';

const UseStateExample = () => {
    const [count, setCount, setTimerOn] = useTimeout()
    return (
        <div>
            <h2>Notes:</h2>
            <p>New function are created on each render</p>
            <br />
            <h2>count = {count}</h2>
            <button onClick={() => setCount(prev => prev + 1)}>Increment</button>
            <br />
            <button onClick={() => setCount(prev => prev - 1)}>Decrement</button>
            <br />
            <button onClick={() => setTimerOn(true)}>Set Interval</button>
            <br />
            <button onClick={() => setTimerOn(false)}>Stop Interval</button>
            <br />

        </div>
    );
}

export default UseStateExample;

4 ответа

Решение

--- добавлено @ 2019-02-11 15:58 ---

Хороший шаблон для использования setInterval с помощью Hooks API:

https://overreacted.io/making-setinterval-declarative-with-react-hooks/


--- исходный ответ ---

Некоторые вопросы:

  1. Не используйте непостоянные переменные в глобальной области видимости любых модулей. Если вы используете два экземпляра этого модуля на одной странице, они будут использовать эти глобальные переменные.

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

Лучший способ в моих мыслях:

import { useState, useEffect } from 'react';

export default (handler, interval) => {
  const [intervalId, setIntervalId] = useState();
  useEffect(() => {
    const id = setInterval(handler, interval);
    setIntervalId(id);
    return () => clearInterval(id);
  }, []);
  return () => clearInterval(intervalId);
};

Пример запуска здесь:

https://codesandbox.io/embed/52o442wq8l?codemirror=1

В этом примере мы добавим пару вещей...

  1. Переключатель вкл / выкл для тайм-аута ("бегущий" аргумент), который полностью включит или выключит его

  2. Функция сброса, позволяющая установить тайм-аут на 0 в любое время:

    Если вызов вызван во время работы, он продолжит работу, но вернется к 0. Если вызов вызван, когда он не запущен, он запустит его.

const useTimeout = (callback, delay, running = true) => {
  // save id in a ref so we make sure we're always clearing the latest timeout
  const timeoutId = useRef('');

  // save callback as a ref so we can update the timeout callback without resetting it
  const savedCallback = useRef();
  useEffect(
    () => {
      savedCallback.current = callback;
    },
    [callback],
  );

  // clear the timeout and start a new one, updating the timeoutId ref
  const reset = useCallback(
    () => {
      clearTimeout(timeoutId.current);

      const id = setTimeout(savedCallback.current, delay);
      timeoutId.current = id;
    },
    [delay],
  );

  // keep the timeout dynamic by resetting it whenever its' deps change
  useEffect(
    () => {
      if (running && delay !== null) {
        reset();

        return () => clearTimeout(timeoutId.current);
      }
    },
    [delay, running, reset],
  );

  return { reset };
};

Итак, в вашем примере выше, мы могли бы использовать его так...

const UseStateExample = ({delay}) => {
    // count logic
    const initCount = 0
    const [count, setCount] = useState(initCount)
    
    const incrementCount = () => setCount(prev => prev + 1)
    const decrementCount = () => setCount(prev => prev - 1)
    const resetCount = () => setCount(initCount)

    // timer logic
    const [timerOn, setTimerOn] = useState(false)
    const {reset} = useTimeout(incrementCount, delay, timerOn)

    const startTimer = () => setTimerOn(true)
    const stopTimer = () => setTimerOn(false)
    
    return (
        <div>
            <h2>Notes:</h2>
            <p>New function are created on each render</p>
            <br />
            <h2>count = {count}</h2>
            <button onClick={incrementCount}>Increment</button>
            <br />
            <button onClick={decrementCount}>Decrement</button>
            <br />
            <button onClick={startTimer}>Set Interval</button>
            <br />
            <button onClick={stopTimer}>Stop Interval</button>
            <br />
            <button onClick={reset}>Start Interval Again</button>
            <br />

        </div>
    );
}

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

        const [count, setCount] = useState(60);

  useEffect(() => {
    if (count > 0) {
      setTimeout(() => {
        setCount(count - 1);
      }, 1000);
    }
  }, [count]);

Демонстрация очистки многих таймеров.

Вы должны объявить и очистить timer.currentвместо .

  1. Объявить sа также timer.
      const [s, setS] = useState(0);
let timer = useRef<NodeJS.Timer>();
  1. Инициализировать таймер в useEffect(() => {}).

  1. Очистить таймер.
      useEffect(() => {
  if (s == props.time) {
    clearInterval(timer.current);
  }
  return () => {};
}, [s]);
Другие вопросы по тегам