React,单击打开后立即发生单击外部事件,防止模态显示

问题描述 投票:0回答:4

单击按钮我希望出现

Modal
Modal
组件添加了一个
eventListener
,因此当您在模态框外单击时它会关闭。在 React 18 中,点击事件触发是因为 before Modal 渲染的按钮点击?如果我改为反应 17,这不会发生。

在这里找到一个CodeSandbox。注意, 当您单击按钮时,

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>
  );
}
javascript reactjs
4个回答
5
投票

您可以调用 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 中,事件可能会在重新渲染发生之前完成传播。


0
投票

Dan Abramov 在此评论中给出了一些建议(https://github.com/facebook/react/issues/24657#issuecomment-1150119055)。

建议1是:在useEffect中,延迟addEventListener的调用,设置一个等待时间为0的setTimeout。例子:

  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 是捕获当前事件并忽略 modal 的 useEffect 中的那个事件。 @yousoumar 在this answerhttps://github.com/microsoft/fluentui/pull/18323

中提供的示例

-1
投票

问题的不同解决方案涉及新的

startTransition
API:https://codesandbox.io/s/gracious-benz-0ey6ol?file=/src/App.js:340-388

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

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


-1
投票

您可以在

if
中更改您的
handleOutsideClick
声明,如下所示。你的不起作用,因为用于打开
button
Modal
,因为它不在
Modal
内,通过了这个
if(!ref?.current?.contains(e.target))
你有。

这是您的 CodeSandbox 的工作叉。我正在将按钮的

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();
  }
};
© www.soinside.com 2019 - 2024. All rights reserved.