想象一下,我们有一个带有复选框的简单的基于函数的组件。
我很好奇为什么后续添加和删除事件侦听器成功:
function Component() {
const handler = () => 123
useEffect(() => {
window.addEventListener("beforeunload", handler)
window.removeEventListener("beforeunload", handler)
}, [isChecked])
}
但当引入一个条件时,例如:
function Component() {
const handler = () => 123
useEffect(() => {
isChecked
? window.addEventListener("beforeunload", handler)
: window.removeEventListener("beforeunload", handler)
}, [isChecked])
}
侦听器已添加,但不再删除。
为什么?是因为我们的处理程序是在重新渲染时重新创建的吗?当处理程序移到组件外部时,一切都井然有序。
这里有一个演示给那些想测试它的人:
https://codesandbox.io/s/y8jono6yx?module=%2Fsrc%2FCheckbox.jsx
说明:
- 下载项目文件(顶部栏菜单(
- 在下载的文件夹中运行
npm i
- 使用
npm start
启动开发服务器
至少在Chrome中,可以通过调用getEventListeners(window)
来检索事件侦听器列表。
问题是handler
是每个组件渲染器上的新函数。window.removeEventListener("beforeunload", handler)
不是操作,因为此handler
函数从未注册为beforeunload
侦听器。
handler
应移动到组件功能范围之外:
const handler = () => 123;
export default function Checkbox() {
const [isChecked, setIsChecked] = useState(false);
useEffect(
() => {
isChecked
? window.addEventListener("beforeunload", handler)
: window.removeEventListener("beforeunload", handler);
},
[isChecked]
);
...
};
由于handler
无法通过这种方式访问组件功能范围,因此在那里可以做的事情非常有限。更传统的方法是使useEffect
回调只执行一次(与componentDidMount
相对应(,并从useEffect
(与componentWillUnmount
相对应(返回函数。由于useEffect
函数的作用域中使用了相同的handler
,因此问题消失了,但另一个问题是handler
不能与useState
一起使用,因为useEffect
中使用的handler
是在初始渲染期间定义的函数,并且isChecked
在其作用域中将始终是false
。useRef
可以用于此目的(演示(:
export default function Checkbox() {
const isCheckedRef = useRef(false);
const handler = () => {
if (isCheckedRef.current)
...
};
useEffect(
() => {
window.addEventListener("beforeunload", handler);
return () => {
window.removeEventListener("beforeunload", handler);
}
},
[]
);
return (
<input
type="checkbox"
checked={isCheckedRef.current}
onChange={e => isCheckedRef.current = e.target.checked}
/>
);
}