javascript React,click outside事件发生在click to open之后,阻止了modal的显示

pod7payv  于 2023-03-21  发布在  Java
关注(0)|答案(4)|浏览(127)

在按钮点击时,我希望Modal出现。Modal组件添加了一个eventListener,因此当你在模态之外点击时,它会关闭。在React 18中,click事件会触发,因为按钮点击发生在之前Modal被渲染?如果我更改为react 17,这不会发生。
找到一个CodeSandbox here。请注意,当您单击按钮时,show的状态设置为true。然后Modal组件直接呈现并调用close函数。
App.js:

import { useState } from "react";
import Modal from "./Modal";
import "./styles.css";

export default function App() {
  const [show, setShow] = useState(false);

  const close = () => {
    setShow(false);
  };

  return (
    <div className="App">
      <h1>Hello CodeSandbox</h1>
      <button
        onClick={(e) => {
          setShow(true);
        }}
      >
        Click
      </button>
      {show && <Modal close={close} />}
    </div>
  );
}

Modal.js

import "./styles.css";
import { useRef, useEffect } from "react";

export default function Modal({ close }) {
  const ref = useRef(null);

  useEffect(() => {
    const handleOutsideClick = (e) => {
      if (!ref?.current?.contains(e.target)) {
        console.log("This one gets called because of the button click", e);
        close();
      }
    };

    document.addEventListener("click", handleOutsideClick, false);
    return () => {
      document.removeEventListener("click", handleOutsideClick, false);
    };
  }, [close]);

  return (
    <div ref={ref} className="Modal">
      <h1>I'm a Modal!</h1>
    </div>
  );
}
gwbalxhn

gwbalxhn1#

您可以调用event.stopPropagation来防止多个单击处理程序捕获同一个单击事件。

onClick={(e) => {
      e.stopPropagation();
      setShow(true);
    }}

我不知道为什么React 17和18之间会有不同。React使用自己的“合成事件”,两个版本之间的事件传播/冒泡方式可能会发生变化。
它可能与React 18中所谓的“自动批处理”有关。https://github.com/reactwg/react-18/discussions/21
在您的示例中,Modal组件使用document.addEventlistener()的本地事件处理。看起来React 18处理应用程序内部的点击,这会触发状态更改和重新呈现,挂载Modal组件,运行useEffect()钩子,并在点击事件传播到窗口节点之前创建新的事件侦听器。在React 17中,事件大概在重新呈现发生之前完成传播。

68de4m5k

68de4m5k2#

Dan Abramov在这篇评论中给出了一些建议(https://github.com/facebook/react/issues/24657#issuecomment-1150119055)。
建议一是:在useEffect中,使用setTimeout延迟addEventListener调用,等待时间为0。示例:

useEffect(() => {
    const handleOutsideClick = (e) => {
      if (!ref?.current?.contains(e.target)) {
        console.log("This one gets called because of the button click", e);
        close();
      }
    };

    const timeoutId = setTimeout(() => {
      document.addEventListener("click", handleOutsideClick, false);
    }, 0);
    return () => {
      clearTimeout(timeoutId);
      document.removeEventListener("click", handleOutsideClick, false);
    };
  });

建议2是捕获当前事件并忽略模态的useEffect中的事件。

qij5mzcb

qij5mzcb3#

另一种解决方案涉及新的startTransition API:https://codesandbox.io/s/gracious-benz-0ey6ol?file=/src/App.js:340-388

onClick={(e) => {
  startTransition(() => {
    setShow(true);
  })
}}

这个新的API(doc)肯定有帮助,但我不确定这是一个合法的解决方案,文档对此也不太清楚。如果React的人能发表评论就好了-这是滥用吗?

2nc8po8w

2nc8po8w4#

你可以在handleOutsideClick中修改你的if语句,如下所示。你的不工作是因为打开Modalbutton,因为它不在Modal中,传递了你的if(!ref?.current?.contains(e.target))
CodeSandbox的Here is a working fork。我将按钮的ref传递给Modal。以下是我更改的内容:

const handleOutsideClick = (e) => {
  if (!ref?.current?.contains(e.target) && !openButtonRef?.current?.contains(e.target)) {
    console.log("This one gets called because of the button click", e);
    close();
  }
};

相关问题