Join the discussion
Comments
Loading comments...
Cookie preferences
We use essential cookies for sign-in and preferences. With your permission, we also use basic analytics to improve lessons.
Join the discussion
Loading comments...
We use essential cookies for sign-in and preferences. With your permission, we also use basic analytics to improve lessons.
在父 DOM 层次结构之外渲染子元素,同时保留 React 事件冒泡。
ReactDOM.createPortal(children, domNode) 将 React 子树渲染到与父组件不同的 DOM 节点中。虽然组件在 DOM 中出现在不同的位置(如 body 层级的模态框),但它在 React 组件树中仍然是子组件。这意味着 context、事件和 React 生命周期的工作方式与传送门是内联的完全相同。
createPortal(children, document.getElementById('modal-root')) 告诉 React 将 children 渲染到 #modal-root 中,而不是父组件的 DOM 子树中。DOM 输出与父组件的 DOM 完全分离。
尽管在 DOM 的其他地方渲染,传送门组件在概念上是渲染它的组件的子组件。它获得相同的 context 值、相同的 React props,并参与同一个 Fiber 树。
如果你点击传送门内部,点击事件会通过 React 的组件树冒泡到父组件 — 即使在 DOM 中,传送门的节点在父组件的 DOM 子树之外。React 的合成事件系统在 Fiber 树上操作,而不是 DOM 树。
传送门组件可以读取来自 Provider 的 context 值,该 Provider 是 React 组件树中的祖先,即使传送门渲染在完全不同的 DOM 节点中。
ReactDOM.createPortal(children, domNode)。将 React children 渲染到 domNode 中而不是父容器中。
两个不同的层次结构。React 树控制 context、事件和 props。DOM 树控制视觉布局和 CSS 继承。
传送门事件通过 React 组件树(而非 DOM 树)冒泡,因此父组件会接收到来自传送门子组件的事件。
最常见的传送门使用场景:将模态框和工具提示渲染到 document.body,以避免祖先元素的 overflow:hidden 或 z-index 裁剪。
1import { createPortal } from 'react-dom';23function Modal({ isOpen, onClose, children }) {4 if (!isOpen) return null;56 // Renders into document.body, not into parent's DOM node7 return createPortal(8 <div className="modal-overlay" onClick={onClose}>9 <div className="modal-content" onClick={e => e.stopPropagation()}>10 {children}11 </div>12 </div>,13 document.body // the target DOM node14 );15}1617// Usage — modal is a React child of Card,18// but renders at document.body in the DOM19function Card() {20 const [open, setOpen] = useState(false);21 return (22 <div style={{ overflow: 'hidden' }}> {/* won't clip modal */}23 <button onClick={() => setOpen(true)}>Open Modal</button>24 <Modal isOpen={open} onClose={() => setOpen(false)}>25 Content here!26 </Modal>27 </div>28 );29}
传送门解决了一个基本的 CSS 问题:模态框、工具提示和下拉菜单需要从父容器中视觉上"逃脱"。祖先 CSS 如 overflow:hidden、z-index 堆叠上下文和 transform 属性可能会裁剪或重新定位绝对定位的子元素。通过在 document.body 渲染,传送门绕过了所有这些约束,同时保留了 React 的组件模型。
Your card component has `overflow: hidden` to clip long text. When you add a dropdown menu or modal inside the card, it gets clipped by the card's overflow — users can't see the full dropdown.
The modal/dropdown is a DOM child of the card, so CSS `overflow: hidden` on the card clips everything inside it. You could remove overflow:hidden but that breaks the text truncation. CSS `z-index` alone can't escape an overflow context.
Use a portal to render the modal at `document.body`. It escapes the card's CSS stacking context entirely. React events still bubble through the React tree (card → modal), so onClick handlers and context providers work normally — only the DOM position changes.
Takeaway: Portals decouple the DOM hierarchy from the React component hierarchy. This is essential for any UI that needs to visually 'break out' of its container: modals, tooltips, dropdowns, toast notifications, and floating menus.
You're building a micro-frontend where your React widget needs to render inside a specific container managed by a host application (not your root div). The host gives you a `#widget-container` div.
Your React app renders into `#app-root` by default. You can't simply move your component tree because it needs to share context and state with other parts of your React app.
Use portals to render specific components into `#widget-container` while keeping them in the same React tree. They still receive context from their React parent, participate in event bubbling, and share state — they just render into a different DOM node.
Takeaway: Portals are the foundation for micro-frontend integration patterns. They let you mount React components into external DOM nodes while preserving the full React component model (context, error boundaries, event delegation).