React popover(modal) 在我单击打开按钮(事件侦听器)后自动关闭

React popover(modal) closes automatically after I click the open button (event listener)

提问人:JXUHO 提问时间:8/25/2023 最后编辑:JXUHO 更新时间:8/25/2023 访问量:86

问:

目标:我想在 React 中实现一个 popover(modal)。 当用户单击按钮时,弹出框将打开。 当用户在弹出框外部单击时关闭弹出框。

逻辑:使用状态有条件地渲染弹出框组件。 在 popover 组件中,添加事件监听器,用于监听文档的 click 事件。 如果用户单击文档,它会将状态更改为 false,以不呈现弹出框组件。

问题:如果我单击按钮打开弹出框,则触发事件侦听器。 这样弹出框就会自动关闭。

我想知道为什么会这样。

import { useState } from "react";
import Popover from "./Popover";

const TaskButton = () => {
  const [isPopoverShown, setIsPopoverShown] = useState(false);

  const popoverOpenHandler = () => {
    setIsPopoverShown(true);
  };

  const popoverCloseHandler = () => {
    setIsPopoverShown(false);
  };

  return (
    <div>
      <button
        onClick={popoverOpenHandler}
      >
        button
      </button>
      {isPopoverShown && (
        <Popover isOpen={isPopoverShown} onClose={popoverCloseHandler}>
          hello
        </Popover>
      )}
    </div>
  );
};

export default TaskButton;

import React, { useEffect, useRef } from "react";

const Popover =({ onClose, children }) => {
  const popoverRef = useRef(null);

  useEffect(() => {
    
    const pageClickEvent = (event) => { 
      if (popoverRef.current !== event.target) { 
        onClose();
      }
    };

    document.addEventListener("click", pageClickEvent); 
    
    return () => {
      document.removeEventListener("click", pageClickEvent);
    };
  }, [onClose]);

  return <div ref={popoverRef}>{children}</div>;
}

export default Popover;

我的想法:

我认为问题来自事件冒泡和捕获。

document.addEventListener("mousedown", pageClickEvent); document.addEventListener("click", pageClickEvent, true);

因为当我更改其中一个代码时,它会按照我的预期工作。

但是我想知道如果我将事件“click”更改为“mousedown”(或向上),为什么它会起作用。 或者,如果我将捕获选项更改为 true。

我认为只有当组件挂载在 DOM 上时,事件侦听器才会被激活。 因此,不应通过单击按钮打开组件本身来触发事件侦听器。

javascript reactjs 侦听 事件冒泡 react-modal

评论


答:

2赞 Tsvetislav Todorov 8/25/2023 #1

由于事件传播,您观察到此行为。

当您单击按钮打开弹出框时,单击事件首先由按钮本身捕获,然后冒泡到文档级别。由于事件侦听器位于文档上,并且正在侦听任何单击事件,因此它会在按钮的单击事件之后立即触发。

若要防止此行为,需要停止将按钮的单击事件传播到文档级别。您可以使用以下方法实现此目的:e.stopPropagation()

const popoverOpenHandler = (e) => {
  e.stopPropagation();
  setIsPopoverShown(true);
};

通过在 中调用,可以防止单击事件传播到按钮本身之外。这意味着文档的单击事件侦听器不会由按钮单击触发,并且当您尝试打开弹出框时,它不会立即关闭。e.stopPropagation()popoverOpenHandler

一些可能有用的其他资源: https://developer.mozilla.org/en-US/docs/Web/API/Event/stopPropagation

我希望这会有所帮助。

评论

0赞 JXUHO 8/25/2023
非常感谢!!它对我帮助很大!我想知道,按钮单击事件发生在子组件呈现之前。子组件中的事件侦听器如何监听按钮点击?
1赞 Tsvetislav Todorov 8/25/2023
您可以将其视为竞争条件。如果不停止传播,你就不能保证 React 不会更新状态并渲染 Popover 的速度比事件气泡到文档级别和触发侦听器的速度更快。因为在你的情况下,点击按钮时并不是一个昂贵的计算,所以 React 总是比事件冒泡快。