Изменение функций прослушивателя событий при использовании перехватчиков React
У меня есть компонент, который использует прослушиватели событий в нескольких местах через addEventListener
а также removeEventListener
. Недостаточно использовать такие компонентные методы, какonMouseMove
потому что мне также нужно обнаруживать события вне компонента.
Я использую хуки в компоненте, некоторые из которых имеют массив зависимостей в конце, в частности useCallback(eventFunction, dependencies)
с функциями событий, которые будут использоваться со слушателями. Зависимости обычно представляют собой переменные с состоянием, объявленные с использованиемuseState
.
Насколько я могу судить, идентичность функции важна в add/remove
EventListener
, так что если функция изменяется между ними, она не работает. Сначала я попытался управлять хуками, чтобы функции событий не меняли идентичность междуadd
а также remove
но это быстро стало громоздким из-за зависимости функций от состояния.
В итоге я пришел к следующему шаблону: поскольку функция-установщик (второй входной параметр для useState
) получает текущее состояние в качестве аргумента, у меня могут быть функции событий, которые никогда не изменяются после первого рендеринга (мы все еще называем это монтирование?), но все же имеют доступ к актуальным переменным с состоянием. Пример:
import React, { useCallback, useEffect, useState } from 'react';
const Component = () => {
const [state, setState] = useState(null);
const handleMouseMove = useCallback(() => {
setState((currentState) => {
// ... do something that involves currentState ...
return currentState;
});
}, []);
useEffect(() => {
window.addEventListener('mousemove', handleMouseMove);
return () => {
window.removeEventListener('mousemove', handleMouseMove);
};
}, [/* ... some parameters here ... */]);
// ... more effects etc ...
return <span>test</span>;
};
(Это очень упрощенная иллюстрация).
Кажется, это работает нормально, но я не уверен, что это кажется правильным - использование функции установки, которая никогда не меняет состояние, а просто как взлом для доступа к текущему состоянию.
Кроме того, для функций событий, которым требуется несколько переменных состояния, я должен вложить вызовы установщика.
Есть ли другой шаблон, который мог бы лучше справиться с этой ситуацией?
2 ответа
Насколько я могу судить, идентичность функции важна при добавлении / удалении EventListener, поэтому, если функция изменяется между ними, она не работает.
Хотя это правда, нам не нужно идти на крайние меры, чтобы функция четной функции вообще не меняла идентичность.
Простыми шагами будут: Объявить функцию события, используя useCallback
- список зависимостей useCallback
должен включать все переменные с состоянием, от которых зависит ваша функция.
Использовать useEffect
чтобы добавить прослушиватель событий. Верните функцию очистки, которая удалит прослушиватель событий. Список зависимостейuseEffect
должен включать саму функцию прослушивателя событий в дополнение к любой другой переменной с состоянием, которую может использовать ваша функция эффекта.
Таким образом, при изменении любой из переменных с состоянием, используемых прослушивателем событий, даже изменения идентификатора слушателя, которые запускают запуск эффекта, но перед запуском будет запущена функция очистки эффекта, возвращенная предыдущим запуском эффекта, правильно удаляя старый прослушиватель событий перед добавлением нового.
Что-то вроде:
const Component = () => {
const [state, setState] = useState();
const eventListner = useCallback(() => {
console.log(state); // use the stateful variable in event listener
}, [state]);
useEffect(() => {
el.addEventListner('someEvent', eventListner);
return () => el.removeEventListener('someEvent', eventListner);
}, [eventListener]);
}
Решение @ckedar может решить этот вопрос, но у него есть проблема с производительностью, когда изменяется eventListener, реакция удалит и добавитEvent на дом。
вы можете использовать
useRef()
вместо
useState()
, если вы хотите изменить состояние прослушивания, вы можете использовать
useStateRef()
:
import React, { useEffect, useRef, useState } from 'react';
export default function useStateRef(initialValue:any): Array<any>{
const [value, setValue] = useState(initialValue);
const ref = useRef(value);
useEffect(() => {
ref.current = value;
},[value])
return [value,setValue,ref];
}