portal 将子元素渲染到 DOM 中的不同位置(在父元素 DOM 层次结构之外),同时将它们保留在 React 的组件树中——因此上下文、props 和事件仍然像正常嵌套一样工作。
jsx
import { createPortal } from "react-dom";
function Modal({ children, onClose }) {
// renders into <div id="modal-root"> at the end of <body>,
// NOT inside whatever component called <Modal/>
return createPortal(
,
.()
);
}
为什么这很重要
模态框、工具提示、下拉菜单和 toast 必须逃脱祖先的 overflow: hidden 或 z-index 堆叠上下文。如果模态框嵌套在具有 overflow: hidden 的卡片内,它会被剪裁。通过 portal 渲染到 body 可以完全避免这种情况。
事件仍然遵循 React 树
这是微妙而强大的部分:即使模态框的 DOM 存在于 body 下,React 祖先上的 onClick 仍然会触发,因为事件冒泡遵循 React 树,而不是 DOM 树:
jsx
<div onClick={handleParentClick}> {/* still fires for clicks inside the portal */}
<Modal>...</Modal>
</div>
不要忘记可访问性
Portal 解决了定位,而不是可访问性。对于对话框,您仍然需要焦点陷阱、role="dialog"/aria-modal 和按 Escape 关闭(或使用原生 <dialog> 元素来处理大部分工作)。
