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
)。由于点击是从鼠标按下,然后是鼠标弹起产生的,因此事件链首先 运行 仅用于门户(内容没有处理程序)。
仅供参考:所提供的解决方案绝对有效,就像 onMouseDown
、onClick
一样,只要您一直使用它也应该有效。
我出于不同的原因看到了同样的问题,只是在最愚蠢的错误上旋转了一段时间:我的 child 事件侦听器中有一个调试器。事实证明,当我单击我的 child 时,我 是 成功地停止了传播并避免了“关闭”行为。然而,当我在 Chrome window 中单击调试器上的“播放”按钮时,我无意中单击了 parent,并看到了 console.log parent appear ♀️ 这出现在控制台中,好像 parent 的 onClick
先出现了,但它实际上与我的调试器的位置有关!
在我的应用程序中,我创建了一个 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
)。由于点击是从鼠标按下,然后是鼠标弹起产生的,因此事件链首先 运行 仅用于门户(内容没有处理程序)。
仅供参考:所提供的解决方案绝对有效,就像 onMouseDown
、onClick
一样,只要您一直使用它也应该有效。
我出于不同的原因看到了同样的问题,只是在最愚蠢的错误上旋转了一段时间:我的 child 事件侦听器中有一个调试器。事实证明,当我单击我的 child 时,我 是 成功地停止了传播并避免了“关闭”行为。然而,当我在 Chrome window 中单击调试器上的“播放”按钮时,我无意中单击了 parent,并看到了 console.log parent appear ♀️ 这出现在控制台中,好像 parent 的 onClick
先出现了,但它实际上与我的调试器的位置有关!