在基于函数的组件内的useEffect调用中未删除事件侦听器



想象一下,我们有一个带有复选框的简单的基于函数的组件。

我很好奇为什么后续添加和删除事件侦听器成功:

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在其作用域中将始终是falseuseRef可以用于此目的(演示(:

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}
/>
);
}

最新更新