createPortal
允许您将一些子元素渲染到 DOM 的不同部分。
<div>
<SomeComponent />
{createPortal(children, domNode, key?)}
</div>
参考
createPortal(children, domNode, key?)
要创建一个传送门,请调用 createPortal
,传入一些 JSX,以及它应该被渲染到的 DOM 节点
import { createPortal } from 'react-dom';
// ...
<div>
<p>This child is placed in the parent div.</p>
{createPortal(
<p>This child is placed in the document body.</p>,
document.body
)}
</div>
传送门只改变 DOM 节点的物理位置。在其他所有方面,您渲染到传送门中的 JSX 都充当渲染它的 React 组件的子节点。例如,子节点可以访问父树提供的上下文,并且事件会根据 React 树从子节点冒泡到父节点。
参数
-
children
: 任何可以用 React 渲染的东西,例如一段 JSX(例如<div />
或<SomeComponent />
),一个 Fragment (<>...</>
),一个字符串或数字,或者一个由这些组成的数组。 -
domNode
: 某个 DOM 节点,例如由document.getElementById()
返回的节点。该节点必须已经存在。在更新期间传入不同的 DOM 节点将导致传送门内容被重新创建。 -
可选
key
: 一个唯一的字符串或数字,用作传送门的 key。
返回值
createPortal
返回一个 React 节点,可以将其包含在 JSX 中或从 React 组件中返回。如果 React 在渲染输出中遇到它,它会将提供的 children
放置在提供的 domNode
中。
注意事项
- 来自 portal 的事件是根据 React 树而不是 DOM 树进行传播的。例如,如果您在 portal 内部点击,并且 portal 包裹在
<div onClick>
中,则会触发该onClick
处理程序。如果这会导致问题,请从 portal 内部阻止事件传播,或者将 portal 本身在 React 树中向上移动。
用法
渲染到 DOM 的不同部分
Portal 允许您的组件将其部分子组件渲染到 DOM 中的不同位置。这可以让您组件的一部分“逃脱”它所在的任何容器。例如,组件可以显示一个模态对话框或一个工具提示,该对话框或工具提示显示在页面的其余部分之上和外部。
要创建一个 portal,请使用 一些 JSX 和 它应该去的 DOM 节点 来渲染 createPortal
的结果
import { createPortal } from 'react-dom';
function MyComponent() {
return (
<div style={{ border: '2px solid black' }}>
<p>This child is placed in the parent div.</p>
{createPortal(
<p>This child is placed in the document body.</p>,
document.body
)}
</div>
);
}
React 会将 您传递的 JSX 的 DOM 节点放入 您提供的 DOM 节点 中。
如果没有 portal,第二个 <p>
将被放置在父级 <div>
中,但 portal 会将其“传送”到 document.body
: 中:
import { createPortal } from 'react-dom'; export default function MyComponent() { return ( <div style={{ border: '2px solid black' }}> <p>This child is placed in the parent div.</p> {createPortal( <p>This child is placed in the document body.</p>, document.body )} </div> ); }
请注意,第二个段落如何在视觉上出现在带有边框的父级 <div>
之外。如果您使用开发者工具检查 DOM 结构,您会看到第二个 <p>
被直接放置到 <body>
中
<body>
<div id="root">
...
<div style="border: 2px solid black">
<p>This child is placed inside the parent div.</p>
</div>
...
</div>
<p>This child is placed in the document body.</p>
</body>
Portal 仅更改 DOM 节点的物理位置。在任何其他方面,您渲染到 portal 中的 JSX 都充当渲染它的 React 组件的子节点。例如,子节点可以访问父树提供的上下文,并且事件仍然会根据 React 树从子节点向上冒泡到父节点。
使用 portal 渲染模态对话框
您可以使用 portal 创建一个浮动在页面其余部分之上的模态对话框,即使调用对话框的组件位于具有 overflow: hidden
或其他干扰对话框的样式的容器中。
在这个例子中,这两个容器的样式会破坏模态对话框,但渲染到 portal 中的容器不受影响,因为在 DOM 中,模态对话框不包含在父级 JSX 元素中。
import NoPortalExample from './NoPortalExample'; import PortalExample from './PortalExample'; export default function App() { return ( <> <div className="clipping-container"> <NoPortalExample /> </div> <div className="clipping-container"> <PortalExample /> </div> </> ); }
将 React 组件渲染到非 React 服务器标记中
如果您的 React 根目录只是静态或服务器渲染页面的一部分,而该页面不是使用 React 构建的,则 portal 非常有用。例如,如果您的页面是使用像 Rails 这样的服务器框架构建的,则您可以在静态区域(如侧边栏)中创建交互区域。与拥有 多个独立的 React 根目录 相比,portal 允许您将应用程序视为具有共享状态的单个 React 树,即使其部分渲染到 DOM 的不同部分。
import { createPortal } from 'react-dom'; const sidebarContentEl = document.getElementById('sidebar-content'); export default function App() { return ( <> <MainContent /> {createPortal( <SidebarContent />, sidebarContentEl )} </> ); } function MainContent() { return <p>This part is rendered by React</p>; } function SidebarContent() { return <p>This part is also rendered by React!</p>; }
将 React 组件渲染到非 React DOM 节点
您还可以使用 portal 来管理 React 之外管理的 DOM 节点的内容。例如,假设您要与一个非 React 地图小部件集成,并且希望在弹出窗口中渲染 React 内容。为此,请声明一个 popupContainer
状态变量来存储您要渲染到的 DOM 节点
const [popupContainer, setPopupContainer] = useState(null);
创建第三方小部件时,请存储小部件返回的 DOM 节点,以便您可以渲染到其中
useEffect(() => {
if (mapRef.current === null) {
const map = createMapWidget(containerRef.current);
mapRef.current = map;
const popupDiv = addPopupToMapWidget(map);
setPopupContainer(popupDiv);
}
}, []);
这使您可以使用 createPortal
将 React 内容渲染到 popupContainer
中(一旦它可用)
return (
<div style={{ width: 250, height: 250 }} ref={containerRef}>
{popupContainer !== null && createPortal(
<p>Hello from React!</p>,
popupContainer
)}
</div>
);
这是一个您可以尝试的完整示例
import { useRef, useEffect, useState } from 'react'; import { createPortal } from 'react-dom'; import { createMapWidget, addPopupToMapWidget } from './map-widget.js'; export default function Map() { const containerRef = useRef(null); const mapRef = useRef(null); const [popupContainer, setPopupContainer] = useState(null); useEffect(() => { if (mapRef.current === null) { const map = createMapWidget(containerRef.current); mapRef.current = map; const popupDiv = addPopupToMapWidget(map); setPopupContainer(popupDiv); } }, []); return ( <div style={{ width: 250, height: 250 }} ref={containerRef}> {popupContainer !== null && createPortal( <p>Hello from React!</p>, popupContainer )} </div> ); }