Краткое описание проблемы : 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
из списка событий решает проблему. Кто-нибудь знает, что происходит под капотом?