在基于函数的组件内的 useEffect 调用中,事件侦听器不会被删除

Pav*_* Ye 4 dom reactjs

假设我们有一个带有复选框的简单的基于函数的组件。

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

function Component() {
  const handler = () => 123

  useEffect(() => {
    window.addEventListener("beforeunload", handler)
    window.removeEventListener("beforeunload", handler)
  }, [isChecked])
}
Run Code Online (Sandbox Code Playgroud)

但是当引入一个条件时,例如:

function Component() {
  const handler = () => 123

  useEffect(() => {
    isChecked
      ? window.addEventListener("beforeunload", handler)
      : window.removeEventListener("beforeunload", handler)
  }, [isChecked])
}
Run Code Online (Sandbox Code Playgroud)

侦听器已添加,但不再删除。

这是为什么?是因为我们的处理程序是在重新渲染时重新创建的吗?当处理程序移出组件时,一切都井然有序。


这是一个演示,供那些想要测试它的人使用:

https://codesandbox.io/s/y8jono6yx?module=%2Fsrc%2FCheckbox.jsx

指示:

  • 下载项目文件(顶部栏菜单)
  • npm i在下载的文件夹中运行
  • 启动开发服务器npm start

事件侦听器列表可以通过调用来检索getEventListeners(window)- 至少在 Chrome 中是这样。

Est*_*ask 6

问题是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]
  );
  ...
};
Run Code Online (Sandbox Code Playgroud)

由于handler无法以这种方式访问​​组件函数作用域,因此可以做的事情非常有限。更传统的方法是使useEffect回调仅执行一次(与 相对应)并从(与 相对应)componentDidMount返回一个函数。由于在函数的作用域中使用了same ,所以问题就消失了,但另一个问题是不能与 一起使用,因为它使用的是在初始渲染期间定义的函数,并且将始终在其作用域内。可以用于此目的(演示useEffectcomponentWillUnmounthandleruseEffecthandleruseStatehandleruseEffectisCheckedfalseuseRef

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}
    />
  );
}
Run Code Online (Sandbox Code Playgroud)