#reactjs #react-hooks #removeeventlistener
#reactjs #реагирующие хуки #removeeventlistener
Вопрос:
В документах React Hooks показано, как удалить EventListener на этапе очистки компонента. https://reactjs.org/docs/hooks-reference.html#conditionally-firing-an-effect
В моем случае использования я пытаюсь удалить условие removeEventListener для свойства состояния функционального компонента.
Вот пример, когда компонент никогда не размонтируется, но прослушиватель событий должен быть удален:
function App () {
const [collapsed, setCollapsed] = React.useState(true);
React.useEffect(
() => {
if (collapsed) {
window.removeEventListener('keyup', handleKeyUp); // Not the same "handleKeyUp" :(
} else {
window.addEventListener('keyup', handleKeyUp);
}
},
[collapsed]
);
function handleKeyUp(event) {
console.log(event.key);
switch (event.key) {
case 'Escape':
setCollapsed(true);
break;
}
}
return collapsed ? (
<a href="javascript:;" onClick={()=>setCollapsed(false)}>Search</a>
) : (
<span>
<input placeholder="Search" autoFocus />amp;nbsp;
<a href="javascript:;">This</a>amp;nbsp;
<a href="javascript:;">That</a>amp;nbsp;
<input placeholder="Refinement" />
</span>
);
}
ReactDOM.render(<App />, document.body.appendChild(document.createElement('div')));
(Живой пример на https://codepen.io/caqu/pen/xBeBMN )
Проблема, которую я вижу, заключается в том, что handleKeyUp
ссылка внутри removeEventListener
меняется каждый раз при рендеринге компонента. Функции handleKeyUp
нужна ссылка на setCollapsed
, поэтому она должна быть заключена в App
. Перемещение handleKeyUp
внутрь useEffect
также, похоже, срабатывает несколько раз и теряет ссылку на оригинал handleKeyUp
.
Как я могу условно отобразить window.removeEventListener с помощью React Hooks без размонтирования компонента?
Комментарии:
Ответ №1:
Вы можете поместить handleKeyUp
функцию внутрь функции, заданной для useEffect
(это рекомендуемый способ сделать это в соответствии с официальной документацией) и добавлять прослушиватель и возвращать функцию очистки только тогда, когда collapsed
значение равно false.
useEffect(() => {
if (collapsed) {
return;
}
function handleKeyUp(event) {
switch (event.key) {
case "Escape":
setCollapsed(true);
break;
}
}
window.addEventListener("keyup", handleKeyUp);
return () => window.removeEventListener("keyup", handleKeyUp);
}, [collapsed]);
Ответ №2:
Ответ Толле может сработать, но объявлять функцию внутри if
— плохая практика.
Это затрудняет отслеживание того, когда функция объявлена, а когда нет. Также это может привести к ошибкам, поскольку функции загружаются.
Есть более аккуратный способ это исправить:
Обернув ваш обработчик событий хуком useCallback.
const [collapsed, setCollapsed] = useState(true)
const handleKeyUp = useCallback((event) => {
if (event.key === "Escape") {
setCollapsed(true)
}
}, [setCollapsed])
useEffect(() => {
if (!collapsed) {
window.addEventListener("keyup", handleKeyUp)
} else {
window.removeEventListener("keyup", handleKeyUp)
}
return () => window.removeEventListener("keyup", handleKeyUp)
}, [collapsed, handleKeyUp])
useCallback
зависит отsetCollapsed
. Это гарантирует, чтоhandleKeyUp
не переопределяется при повторной загрузке компонента (что всегда происходит при изменении состояния)useEffect
будет условно добавлять / удалять прослушиватель событий, в противном случае события будут продолжать запускаться до тех пор, пока компонент смонтирован.
Если вы используете много обработчиков событий в useEffect, для этого есть пользовательский хук:https://usehooks.com/useEventListener /
Вот пример постеров вопросов, обновленный моим решением:https://codepen.io/publicJorn/pen/eYzwENN
Комментарии:
1. Очень полезно! Похоже, что у вас в
useEffect
есть дополнительные закрывающие фигурные скобки.2. Это именно то, что я искал, большое спасибо @publicJorn!
3. Зачем ставить
setCollapsed
в качестве аргументаuseCallback
? Разве это не было бы эквивалентно просто do[]
?4. Что определяет
event
приведенный выше пример? @publicJorn5. На самом деле ответ Толле лучше, если это возможно. Предпочитаю определять обратный вызов внутри useEffect, пока это возможно. Тогда вам не нужно беспокоиться о useCallback. Как предлагает Кент Си Доддс: «Если вам необходимо определить функцию для вызова вашего эффекта, то делайте это внутри обратного вызова эффекта, а не снаружи». epicreact.dev/myths-about-useeffect