createPortal

createPortal 允许你将一些子元素渲染到 DOM 的不同部分。

<div> <SomeComponent /> {createPortal(children, domNode, key?)} </div>


参考

createPortal(children, domNode, key?)

调用 createPortal 创建 portal,并传入 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>

请查看以下更多示例

portal 只改变 DOM 节点的所处位置。在其他方面,传入 portal 中的 JSX 将作为渲染它的 React 组件的子节点。该子节点可以访问由父节点树提供的 context 对象、事件将从子节点冒泡到父节点树,以及遵循 React 树的规则。

参数

  • children:React 可以渲染的任何内容,例如 JSX 片段(<div /><SomeComponent /> 等等)、Fragment<>...</>)、字符串或数字,以及这些内容构成的数组。

  • domNode:某个 DOM 节点,例如由 document.getElementById() 返回的节点。节点必须已经存在。在更新过程中传递不同的 DOM 节点将导致 portal 内容被重新创建。

返回值

  • 可选的 key: 用作 portal key 的独特字符串或数字。

createPortal 返回一个 React 节点,该节点可以包含在 JSX 中或从 React 组件中返回。如果 React 在渲染输出中遇见它,它将把提供的 children 放入提供的 domNode 中。

警告

  • portal 中的事件传播遵循 React 树而不是 DOM 树。例如点击 <div onClick> 内部的 portal,将触发 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 组件的子节点。该子节点可以访问由父节点树提供的 context 对象、事件将仍然从子节点冒泡到父节点树。


使用 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> </> ); }

陷阱

使用 portal 时,确保应用程序的无障碍性非常重要。例如,你可能需要管理键盘焦点,以便用户可以以自然的方式进出 portal。

创建模态对话框时,请遵循 WAI-ARIA 模态实践指南。如果你使用了社区包,请确保它是无障碍的,并遵循这些指南。


将 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 state 变量来存储要渲染到的目标 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); } }, []);

这样,一旦 popupContainer 可用,就可以使用 createPortal 将 React 内容渲染到其中:

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> ); }