如何处理使用 ReactDOM.createPortal() 的子级上的 ref.current.contains() ?

joh*_*pin 5 javascript reactjs react-hooks

我使用 React hooks 创建一个应用程序。我有一个辅助函数,当您单击提供使用的组件外部时,该函数onClickOutsideHook(ref, callback)会触发:callbackrefReact.useRef

export const onClickOutsideHook = (ref, callback) => {
  // Hook get from https://stackoverflow.com/a/42234988/8583669

  React.useEffect(() => {
    const handleClickOutside = event => {
      if (ref?.current && !ref.current.contains(event.target)) {
        callback();
      }
    };
    // Bind the event listener
    document.addEventListener("mousedown", handleClickOutside);
    return () => {
      // Unbind the event listener on clean up
      document.removeEventListener("mousedown", handleClickOutside);
    };
  }, [callback, ref]);
};
Run Code Online (Sandbox Code Playgroud)

我有一个Dropdown使用此帮助程序的组件,因此当您在其外部单击时它会关闭。该组件有一个Modal使用 的子组件ReactDOM.createPortal。我用它来渲染, Modal以便body它可以覆盖所有应用程序屏幕。我的Modal包含一个按钮,当您单击它时会发出警报消息:

function Modal() {
  return ReactDOM.createPortal(
    <div
      style={{
        position: "absolute",
        top: 0,
        left: 0,
        height: "100%",
        width: "100%",
        background: "rgba(0,0,0,0.6)"
      }}
    >
      <button onClick={() => alert("Clicked on Modal")}>Click</button>
    </div>,
    document.body
  );
}

function Dropdown(props) {
  const [isModalOpen, setIsModalOpen] = React.useState(false);

  const dropdownRef = React.useRef(null);
  onClickOutsideHook(dropdownRef, props.onClose);

  return (
    <div ref={dropdownRef}>
      Click outside and I will close
      <button onClick={() => setIsModalOpen(true)}>open Modal</button>
      {isModalOpen ? <Modal /> : <></>}
    </div>
  );
}
Run Code Online (Sandbox Code Playgroud)

问题是,当我单击Modal按钮触发警报时,该按钮Dropdown之前已关闭,因为我在其外部单击(Modal不渲染为 的子项,Dropdown而是渲染为body)。所以我的警报永远不会被触发。

有没有一种方法可以定义为usingModal的子级,但仍然在using中渲染它?DropdownrefbodyReactDOM.createPortal

只需查看CodeSandbox即可。

And*_*rei 3

就像门户文档所说:

尽管门户可以位于 DOM 树中的任何位置,但它在其他方面的行为都像普通的 React 子级......

这包括事件冒泡。从门户内部触发的事件将传播到包含 React 树中的祖先,即使这些元素不是 DOM 树中的祖先。

但这里不是这样,mousedown事件监听器被添加到文档上,而不仅仅是Dropdown组件。即便如此,冒泡现象仍然发生。

这意味着,如果您向Modal添加引用,然后在mousedown事件上添加事件侦听器,其全部目的是停止传播,则永远不会调用handleClickOutside函数。

这似乎仍然是一种解决方法,我不知道是否有适当的方法来检查。

function Modal() {
  const modalRef = useRef();

  useEffect(() => {
    const stopPropagation = e => {
      e.stopPropagation();
    };

    const { current: modalDom } = modalRef;
    modalDom.addEventListener("mousedown", stopPropagation);

    return () => {
      modalDom.removeEventListener("mousedown", stopPropagation);
    };
  }, []);

  return ReactDOM.createPortal(
    <div
      ref={modalRef}
      style={{
        position: "absolute",
        top: 0,
        left: 0,
        height: "100%",
        width: "100%",
        background: "rgba(0,0,0,0.6)"
      }}
    >
      <button onClick={() => alert("Clicked on Modal")}>Click</button>
    </div>,
    document.body
  );
}
Run Code Online (Sandbox Code Playgroud)

从以下CodeSandbox中观察 Modal 组件。