React Portals 的冒泡事件不受 DOM 层级影响

FreeGuideOnline 最新 2026-07-06

jsx import { createPortal } from 'react-dom';

function Modal({ children }) { return createPortal(

{children}
, document.getElementById('modal-root') ); }


## 事件冒泡在 React 中是如何工作的

React 中的事件系统是合成的(SyntheticEvent),所有事件处理器都会被绑定到根节点上,并通过事件委托统一管理。但 React 保证事件处理器的调用顺序仍然符合**组件树**的层级结构,而不是真实的 DOM 结构。

当你在一个组件内部触发事件时,事件会沿着组件树向上冒泡,依次调用所有祖先组件上注册的同类型事件处理函数,就像普通 DOM 事件冒泡一样。

## Portals 中的冒泡行为:不受 DOM 层级影响

关键点:**Portal 内部触发的事件会冒泡到 React 组件树中的祖先组件,而不是 DOM 树中的祖先节点。**

也就是说,即使 Portal 渲染的 DOM 节点被放置在完全不同的 DOM 分支下,它的冒泡依然遵循 React 组件的嵌套关系。

### 示例场景

假设组件结构如下:

```jsx
function App() {
  return (
    <div onClick={() => console.log('App clicked')}>
      <Parent />
    </div>
  );
}

function Parent() {
  return (
    <div onClick={() => console.log('Parent clicked')}>
      <Modal>
        <button>点击我</button>
      </Modal>
    </div>
  );
}

function Modal({ children }) {
  return createPortal(
    <div className="modal">{children}</div>,
    document.getElementById('modal-root')
  );
}
  • App 渲染一个 div,内部包含 Parent
  • Parent 内部使用了 Modal,而 Modal 将内容传送到 #modal-root 容器中。
  • 真实 DOM 结构:#modal-root 位于 Appdiv 之外,甚至可能在 <body> 直接子级。

现在点击 <button>,控制台会输出什么?

Parent clicked
App clicked