setTimeout не очищается с помощью React useEffect на мобильных устройствах - PullRequest
0 голосов
/ 06 января 2019

Краткое описание проблемы : setTimeout не очищается на мобильных устройствах при использовании перехватчика React * useEffect. Однако они очищаются на рабочем столе.

Проблема воспроизведения : https://codepen.io/amliving/pen/QzmPYE.

Примечание: запустите на мобильном устройстве, чтобы воспроизвести проблему.

Мой вопрос : Почему мое решение (объяснено ниже) работает?

Детали : Я создаю пользовательский хук для обнаружения безделья. Давайте назовем это useDetectIdle. Он динамически добавляет и удаляет прослушиватели событий к window из набора событий, которые при срабатывании вызывают предоставленный обратный вызов через определенный промежуток времени, через setTimeout.

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

const EVENTS = [
  "scroll",
  "keydown",
  "keypress",
  "touchstart",
  "touchmove",
  "mousedown", /* removing 'mousedown' for mobile devices solves the problem */
];

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

const useDetectIdle = (inactivityTimeout, onIdle) => {
  const timeoutRef = useRef(null);
  const callbackRef = useRef(onIdle);

  useEffect(() => {
    callbackRef.current = onIdle;
  });

  const reset = () => {
    if (timeoutRef.current) {
      clearTimeout(timeoutRef.current);
    }
    const id = setTimeout(callbackRef.current, inactivityTimeout);
    timeoutRef.current = id;
  };

  useEffect(() => {
    reset();

    const handleEvent = _.throttle(() => {
      reset();
    }, 1000);

    EVENTS.forEach(event => window.addEventListener(event, handleEvent));

    return () => {
      EVENTS.forEach(event => window.removeEventListener(event, handleEvent));
      timeoutRef.current && clearTimeout(timeoutRef.current);
    };
  }, []);
};

useDetectIdle вызывается внутри компонентов следующим образом:

const Example = () => {
  useDetectIdle(5000, () => alert("first"));
  return <div className="first">FIRST</div>;
};

На устройствах без сенсорного экрана useDetectIdle работает отлично. Но на мобильных устройствах (как iOS, так и Android) любой существующий тайм-аут не очищается, когда его вызывающий компонент отключается. То есть обратный вызов, переданный setTimemout, все еще срабатывает.

Мое решение : Путем проб и ошибок я обнаружил, что удаление mousedown из списка событий решает проблему. Кто-нибудь знает, что происходит под капотом?

...