React Hooks:如何清理事件监听器?

React Hooks: How to clean event listeners?

我有一个组件 Example,当本地状态 display 变为 false[ 时,我想在其中清除 事件侦听器 show 属性为 true 时,=43=],display 设置为 true,反之亦然。

代码:

const Example = ({ show, onClose }) => {
  const [display, setDisplay] = useState(false);

  const handleClick = (e) => {
    onClose();
    console.log(display);
  };

  useEffect(() => {
    if (display) {
      document.addEventListener('click', handleClick);
    } else {
      document.removeEventListener('click', handleClick);
    }
  }, [display, handleClick]);

  useEffect(() => {
    if (show) {
      setDisplay(show);
    } else {
      setDisplay(false);
    }
  }, [show]);

  return <div>{display && <p>Hi</p>}</div>;
};

问题:

  • 组件无法清除事件侦听器,因为每次都会对函数 handleClick() 进行 新引用 Example 渲染。
  • 每次调用 handleClick() 时,它都会将 display 记录为 true
  • 7-8 次鼠标点击后,控制台的日志 count 达到 7-8K.

我在这里做错了什么?谢谢:)

为了清理一个useEffect,return一个effect中的函数。每次重新运行效果和卸载组件时都会调用该函数。

这将允许正确删除事件侦听器的原因是因为 useEffect 在 handleClick 函数的当前版本上创建了一个闭包。这允许清理函数具有相同的引用,因此可以正确清理它。它在原始版本中不起作用,因为每次 useEffect 重新 运行 时,都会关闭 handleClick 的新版本,然后尝试使用新版本进行清理。

useEffect(() => {
  if (!display) {
    return;
  }

  document.addEventListener('click', handleClick);

  return () => document.removeEventListener('click', handleClick);
}, [display, handleClick]);

您可以通过使用对 handleClick 函数的引用来进一步降低这种效果的发生频率。

例如,这是最基本的。尽管您可以轻松地将一些参考和额外的使用效果抽象为一个单独的挂钩。

const handleClickRef = useRef(handleClick);

useEffect(()=>{
  handleClickRef.current = handleClick;
},[handleClick])

useEffect(() => {
  if (!display) {
    return;
  }
  const funct = (evt)=>handleClickRef.current(evt);
  document.addEventListener('click',funct);

  return () => document.removeEventListener('click', funct);
}, [display]);