React 门户事件冒泡方向错误

React portal event bubbling in the wrong direction

在我的应用程序中,我创建了一个 React 门户,它包含一个覆盖整个文档的 div 并包含一个 div(以及一些其他内容):

public render(): React.ReactNode {
    const { children, container = document.body } = this.props;
    const { open, options } = this.state;

    const className = this.getEffectiveClassNames([
        "portal",
        this.classFromProperty(options?.ignoreMouseEvents, "ignoreMouse"),
    ]);

    return (
        open && createPortal(
            <div
                ref={this.backgroundRef}
                className={className}
                style={{ "--background-opacity": options?.backgroundOpacity }}
                onMouseDown={this.handlePortalClick}
                {...this.unhandledProperties}
            >
                {children}
            </div>, container,
        )
    );
}

我想在用户点击背景时隐藏传送门div(不是内容):

private handlePortalClick = (e: React.MouseEvent): void => {
    const { open, options } = this.state;

    if (open && options.closeOnPortalClick && e.target === this.backgroundRef.current) {
        this.close();
    }
};

我注册了 2 个鼠标按下处理程序:一个在背景 div 上,一个在内容上。当我单击内容时,我首先获得背景 div 的 onMouseDown 事件,然后触发内容的处理程序。这很奇怪,因为内容是背景的子项并且我没有使用捕获事件处理程序。在这种情况下,我希望首先在内容处理程序中获取事件,然后在容器中获取事件。

现在我必须明确检查事件目标是否是背景 div,这有效,但不是必需的。有人可以解释为什么在这种情况下事件调用顺序是相反的吗?

我已经为您描述的设置创建了一个最小示例,但它没有显示描述的行为。 child 的处理程序首先被调用:

const Child = () => {
  const handleMouseDown = e => console.log("child", e.currentTarget);

  return <div className="content" onMouseDown={handleMouseDown} />;
};

const Portal = ({ children }) => {
  const handleMouseDown = e => console.log("portal", e.currentTarget);

  return createPortal(
    <div className="background" onMouseDown={handleMouseDown}>
      {children}
    </div>,
    document.getElementsByTagName("body")[0]
  );
};

export default function App() {
  return (
    <div className="App">
      <Portal>
        <Child />
      </Portal>
    </div>
  );
}

如果您不想在单击内容时调用后台处理程序,您可以将 event.stopPropagation() 添加到 child 中的处理程序:

const Child = () => {
  const handleMouseDown = e => {
      e.stopPropagation();
      console.log("child", e.currentTarget);
  }

  return <div className="content" onMouseDown={handleMouseDown} />;
};

不当行为的原因是一个简单的错误。门户点击处理程序未连接到 onClick,而是连接到 onMouseDown(而内容事件处理程序设置为 onClick)。由于点击是从鼠标按下,然后是鼠标弹起产生的,因此事件链首先 运行 仅用于门户(内容没有处理程序)。

仅供参考:所提供的解决方案绝对有效,就像 onMouseDownonClick 一样,只要您一直使用它也应该有效。

我出于不同的原因看到了同样的问题,只是在最愚蠢的错误上旋转了一段时间:我的 child 事件侦听器中有一个调试器。事实证明,当我单击我的 child 时,我 成功地停止了传播并避免了“关闭”行为。然而,当我在 Chrome window 中单击调试器上的“播放”按钮时,我无意中单击了 parent,并看到了 console.log parent appear ‍♀️ 这出现在控制台中,好像 parent 的 onClick 先出现了,但它实际上与我的调试器的位置有关!