реагировать на хуки и setInterval - PullRequest
0 голосов
/ 31 декабря 2018

Есть ли какая-либо альтернатива просто держать "часы" в фоновом режиме, чтобы реализовать авто-следующее (через несколько секунд) в карусели с использованием ловушек реагирования?

В настраиваемой ловушке реагирования ниже реализовано состояние дляКарусель, которая поддерживает ручной (следующий, предыдущий, сброс) и автоматический (запуск, остановка) методы изменения текущего (активного) индекса карусели.

const useCarousel = (items = []) => {
  const [current, setCurrent] = useState(
    items && items.length > 0 ? 0 : undefined
  );

  const [auto, setAuto] = useState(false);

  const next = () => setCurrent((current + 1) % items.length);
  const prev = () => setCurrent(current ? current - 1 : items.length - 1);
  const reset = () => setCurrent(0);
  const start = _ => setAuto(true);
  const stop = _ => setAuto(false);


useEffect(() => {
    const interval = setInterval(_ => {
      if (auto) {
        next();
      } else {
        // do nothing
      }
    }, 3000);
    return _ => clearInterval(interval);
  });

  return {
    current,
    next,
    prev,
    reset,
    start,
    stop
  };
};

Ответы [ 4 ]

0 голосов
/ 11 февраля 2019

Вы можете использовать useTimeout ловушку, которая возвращает true через указанное количество миллисекунд.

0 голосов
/ 31 декабря 2018

Пока что кажется, что оба нижеприведенных решения работают должным образом:

Условно создающий таймер - для работы требуется, чтобы useEffect зависело от auto и current

useEffect(
    () => {
      if (!auto) return;
      const interval = setInterval(_ => {
        next();
      }, autoInterval);
      return _ => clearInterval(interval);
    },
    [auto, current]
  );

Условно выполняется обновление до состояния - не требуется использовать зависимости useEffect

useEffect(() => {
    const interval = setInterval(_ => {
      if (auto) {
        next();
      } else {
        // do nothing
      }
    }, autoInterval);
    return _ => clearInterval(interval);
  });

Оба решения работают, если мы заменим setInterval на setTimeout

0 голосов
/ 31 декабря 2018

Существуют различия между setInterval и setTimeout, которые вы, возможно, не захотите потерять, всегда перезапуская таймер при повторной визуализации компонента. Эта скрипка показывает разницу в смещении между двумя (и это даже не учитывает все вычисления, которые выполняет React, которые могут отбросить все это немного дальше).

Обращаясь теперь к вашему ответу , Марко, использование setInterval полностью потеряно, потому что эффекты без условий располагаются и перезапускаются каждый раз, когда компонент перерисовывает.Итак, в вашем первом примере использование зависимости current приводит к тому, что этот эффект удаляется и перезапускается каждый раз, когда изменяется current (каждый раз, когда выполняется интервал).Второй делает то же самое, но фактически каждый раз, когда изменяется любое состояние (вызывая повторный рендеринг), что может привести к неожиданному поведению.Единственная причина, по которой он работает, заключается в том, что next() вызывает изменение состояния.

Учитывая тот факт, что вы, вероятно, не имеете отношения к точному времени, лучше всего использовать setTimeout впростой способ, используя переменные current и auto в качестве зависимостей.Итак, чтобы перефразировать часть вашего ответа, сделайте следующее:

useEffect(
  () => {
    if (!auto) return;
    const interval = setInterval(_ => {
      next();
    }, autoInterval);
    return _ => clearInterval(interval);
  },
  [auto, current]
);

Обратите внимание, что я считаю, что вы можете заменить зависимость current на next, так как это более прямо представляет васделать внутри useEffect.Но я не уверен на 100%, как React различает эти зависимости, поэтому я оставляю все как есть.

Обычно для тех, кто просто читает этот ответ и хочет получить простой таймер, вотверсия, в которой не учитывается исходный код OP, а также необходимость в способе запуска и остановки таймера независимо:

const [counter, setCounter] = useState(0);
useEffect(
  () => {
    const id= setTimeout(() => {
      setCounter(counter + 1);
    }, 1000);
    return () => {
      clearTimer(id);
    };
  },
  [counter],
);

Однако вам может быть интересно, как использовать более точный интервал, учитывая тот факт, что setTimeout может дрейфовать больше, чем setInterval.Вот еще один метод, универсальный, без использования кода ОП:

const [counter, setCounter] = useState(30);
const r = useRef(null);
r.current = { counter, setCounter };
useEffect(
  () => {
    const id = setInterval(() => {
      r.current.setCounter(r.current.counter + 1);
    }, 1000);
    return () => {
      clearInterval(id);
    };
  },
  ['once'],
);

Что здесь происходит?Ну, чтобы обратный вызов setInterval всегда ссылался на приемлемую на данный момент версию setCounter, нам нужно некоторое изменяемое состояние.Реакт дает нам это с useRef.Функция useRef вернет объект, обладающий свойством current.Затем мы можем установить это свойство (которое будет происходить каждый раз при повторном рендеринге компонента) для текущих версий counter и setCounter.

Затем, чтобы предотвратить удаление интервала при каждом рендеринге.мы добавляем зависимость к useEffect, которая гарантированно никогда не изменится.В моем случае мне нравится использовать строку "once", чтобы указать, что я заставляю этот эффект быть установленным только один раз.Интервал будет по-прежнему удаляться, когда компонент отключен.

Таким образом, применяя то, что мы знаем к исходному вопросу ОП, вы можете использовать setInterval для слайд-шоу с меньшей вероятностью дрейфа, подобного этому:

// ... OP's implementation code including `autoInterval`,
// `auto`, and `next` goes above here ...

const r = useRef(null);
r.current = { next };
useEffect(
  () => {
    if (!auto) return;
    const id = setInterval(() => {
      r.current.next();
    }, autoInterval);
    return () => {
      clearInterval(id);
    };
  },
  [auto],
);
0 голосов
/ 31 декабря 2018

Поскольку значение current будет меняться на каждом «интервале», пока оно должно работать, тогда ваш код будет запускать и останавливать новый таймер при каждом рендеринге.Вы можете увидеть это в действии здесь:

https://codesandbox.io/s/03xkkyj19w

Вы можете изменить setInterval на setTimeout, и вы получите точно такое же поведение.setTimeout не являются постоянными часами, но это не имеет значения, так как они оба все равно будут очищены.

Если вы вообще не хотите запускать таймер, тогда поставьте условие перед setInterval невнутри него.

  useEffect(
    () => {
      let id;

      if (run) {
        id = setInterval(() => {
          setValue(value + 1)
        }, 1000);
      }

      return () => {
        if (id) {
          alert(id) // notice this runs on every render and is different every time
          clearInterval(id);
        }
      };
    }
  );
...