问题
In React Hooks documents it is shown how to removeEventListener during the component's cleanup phase. https://reactjs.org/docs/hooks-reference.html#conditionally-firing-an-effect
In my use case, I am trying to removeEventListener conditional to a state property of the functional component.
Here's an example where the component is never unmounted but the event listener should be removed:
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 />
<a href="javascript:;">This</a>
<a href="javascript:;">That</a>
<input placeholder="Refinement" />
</span>
);
}
ReactDOM.render(<App />, document.body.appendChild(document.createElement('div')));
(Live sample at https://codepen.io/caqu/pen/xBeBMN)
The problem I see is that the handleKeyUp
reference inside removeEventListener
is changing every time the component renders. The function handleKeyUp
needs a reference to setCollapsed
so it must be enclosed by App
. Moving handleKeyUp
inside useEffect
also seems to fire multiple times and lose the reference to the original handleKeyUp
.
How can I conditionally window.removeEventListener using React Hooks without unmounting the component?
回答1:
You can put the handleKeyUp
function inside of the function given to useEffect
and only add the listener and return a cleanup function when collapsed
is false.
useEffect(() => {
if (!collapsed) {
function handleKeyUp(event) {
switch (event.key) {
case "Escape":
setCollapsed(true);
break;
}
}
window.addEventListener("keyup", handleKeyUp);
return () => window.removeEventListener("keyup", handleKeyUp);
}
}, [collapsed]);
回答2:
Tholle's answer may work, but it's bad practise to declare a function inside an if
. It makes it harder to follow when the function is declared and when it is not. Also it can lead to bugs because functions are hoisted up.
There's a neater way to fix it:
By wrapping your event handler with the useCallback hook.
const [collapsed, setCollapsed] = useState(true)
const handleKeyUp = useCallback(() => {
switch (event.key) {
case "Escape":
setCollapsed(true)
break
}
}, [setCollapsed])
useEffect(() => {
window.addEventListener("keyup", handleKeyUp)
// See note below
return () => window.removeEventListener("keyup", handleKeyUp)
}, [collapsed])
If you use a lot of event handlers in useEffect, there's a custom hook for that: https://usehooks.com/useEventListener/
Note:
Since you are listening to keyup
, you might even want to remove the event listener when collapsed === true
, because there's nothing to collapse. Otherwise events will keep firing as long as the component is mounted.
useEffect(() => {
if (!collapsed) {
window.addEventListener("keyup", handleKeyUp)
} else {
window.removeEventListener("keyup", handleKeyUp)
}
return () => window.removeEventListener("keyup", handleKeyUp)
}, [collapsed]);
来源:https://stackoverflow.com/questions/55360736/how-do-i-window-removeeventlistener-using-react-useeffect