createPortal
允许你将一些子级渲染到 DOM 的不同部分。
¥createPortal
lets you render some children into a different part of the DOM.
<div>
<SomeComponent />
{createPortal(children, domNode, key?)}
</div>
参考
¥Reference
createPortal(children, domNode, key?)
要创建门户,请调用 createPortal
,传递一些 JSX 和应该渲染它的 DOM 节点:
¥To create a portal, call createPortal
, passing some JSX, and the DOM node where it should be rendered:
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 树从子级冒泡到父级。
¥A portal only changes the physical placement of the DOM node. In every other way, the JSX you render into a portal acts as a child node of the React component that renders it. For example, the child can access the context provided by the parent tree, and events bubble up from children to parents according to the React tree.
参数
¥Parameters
-
children
:任何可以用 React 渲染的东西,例如一段 JSX(例如<div />
或<SomeComponent />
)、分段(<>...</>
)、字符串或数字,或这些的数组。¥
children
: Anything that can be rendered with React, such as a piece of JSX (e.g.<div />
or<SomeComponent />
), a Fragment (<>...</>
), a string or a number, or an array of these. -
domNode
:一些 DOM 节点,例如document.getElementById()
返回的那些。该节点必须已经存在。在更新期间传递不同的 DOM 节点将导致重新创建门户内容。¥
domNode
: Some DOM node, such as those returned bydocument.getElementById()
. The node must already exist. Passing a different DOM node during an update will cause the portal content to be recreated. -
可选
key
:用作门户 键。 的唯一字符串或数字¥optional
key
: A unique string or number to be used as the portal’s key.
返回
¥Returns
createPortal
返回一个可以包含在 JSX 中或从 React 组件返回的 React 节点。如果 React 在渲染输出中遇到它,它将把提供的 children
放在提供的 domNode
中。
¥createPortal
returns a React node that can be included into JSX or returned from a React component. If React encounters it in the render output, it will place the provided children
inside the provided domNode
.
注意事项
¥Caveats
-
来自门户的事件根据 React 树而不是 DOM 树传播。例如,如果你在门户内部单击,并且门户被封装在
<div onClick>
中,那么onClick
处理程序将会触发。如果这导致问题,请停止从门户内部传播事件,或者将门户本身在 React 树中向上移动。¥Events from portals propagate according to the React tree rather than the DOM tree. For example, if you click inside a portal, and the portal is wrapped in
<div onClick>
, thatonClick
handler will fire. If this causes issues, either stop the event propagation from inside the portal, or move the portal itself up in the React tree.
用法
¥Usage
渲染到 DOM 的不同部分
¥Rendering to a different part of the DOM
门户让你的组件将它们的一些子级渲染到 DOM 中的不同位置。这允许你的组件 “escape” 的一部分来自它可能位于的任何容器。例如,一个组件可以显示一个模态对话框或一个出现在页面其余部分之上和之外的工具提示。
¥Portals let your components render some of their children into a different place in the DOM. This lets a part of your component “escape” from whatever containers it may be in. For example, a component can display a modal dialog or a tooltip that appears above and outside of the rest of the page.
要创建门户,请使用 一些 JSX 和 它应该去的 DOM 节点 渲染 createPortal
的结果:
¥To create a portal, render the result of createPortal
with some JSX and the DOM node where it should go:
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 节点 中。
¥React will put the DOM nodes for the JSX you passed inside of the DOM node you provided.
如果没有入口,第二个 <p>
将放置在父 <div>
中,但入口 “teleported” 将放置在 document.body
: 中
¥Without a portal, the second <p>
would be placed inside the parent <div>
, but the portal “teleported” it into the 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>
:
¥Notice how the second paragraph visually appears outside the parent <div>
with the border. If you inspect the DOM structure with developer tools, you’ll see that the second <p>
got placed directly into the <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>
门户仅更改 DOM 节点的物理位置。在所有其他方式中,你渲染到门户中的 JSX 充当渲染它的 React 组件的子节点。例如,子级可以访问父级树提供的上下文,并且事件仍然根据 React 树从子级冒泡到父级。
¥A portal only changes the physical placement of the DOM node. In every other way, the JSX you render into a portal acts as a child node of the React component that renders it. For example, the child can access the context provided by the parent tree, and events still bubble up from children to parents according to the React tree.
使用门户渲染模态对话框
¥Rendering a modal dialog with a portal
你可以使用门户创建一个浮动在页面其余部分上方的模式对话框,即使调用对话框的组件位于具有 overflow: hidden
或其他干扰对话框的样式的容器内。
¥You can use a portal to create a modal dialog that floats above the rest of the page, even if the component that summons the dialog is inside a container with overflow: hidden
or other styles that interfere with the dialog.
在此示例中,两个容器的样式会破坏模态对话框,但渲染到门户中的那个不受影响,因为在 DOM 中,模态不包含在父 JSX 元素中。
¥In this example, the two containers have styles that disrupt the modal dialog, but the one rendered into a portal is unaffected because, in the DOM, the modal is not contained within the parent JSX elements.
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 服务器标记中
¥Rendering React components into non-React server markup
如果你的 React 根目录只是不是使用 React 构建的静态或服务器渲染页面的一部分,则门户网站可能很有用。例如,如果你的页面是使用 Rails 等服务器框架构建的,你可以在侧边栏等静态区域中创建交互区域。与拥有 多个独立的 React 根, 门户相比,你可以将应用视为具有共享状态的单个 React 树,即使它的部分渲染到 DOM 的不同部分。
¥Portals can be useful if your React root is only part of a static or server-rendered page that isn’t built with React. For example, if your page is built with a server framework like Rails, you can create areas of interactivity within static areas such as sidebars. Compared with having multiple separate React roots, portals let you treat the app as a single React tree with shared state even though its parts render to different parts of the 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 节点
¥Rendering React components into non-React DOM nodes
你还可以使用门户来管理在 React 外部管理的 DOM 节点的内容。例如,假设你正在与一个非 React 地图小部件集成,并且你希望在弹出窗口中渲染 React 内容。为此,声明一个 popupContainer
状态变量来存储要渲染到的 DOM 节点:
¥You can also use a portal to manage the content of a DOM node that’s managed outside of React. For example, suppose you’re integrating with a non-React map widget and you want to render React content inside a popup. To do this, declare a popupContainer
state variable to store the DOM node you’re going to render into:
const [popupContainer, setPopupContainer] = useState(null);
当你创建第三方小部件时,存储小部件返回的 DOM 节点,以便你可以渲染到其中:
¥When you create the third-party widget, store the DOM node returned by the widget so you can render into it:
useEffect(() => {
if (mapRef.current === null) {
const map = createMapWidget(containerRef.current);
mapRef.current = map;
const popupDiv = addPopupToMapWidget(map);
setPopupContainer(popupDiv);
}
}, []);
这让你可以使用 createPortal
在 React 内容可用后将其渲染到 popupContainer
中:
¥This lets you use createPortal
to render React content into popupContainer
once it becomes available:
return (
<div style={{ width: 250, height: 250 }} ref={containerRef}>
{popupContainer !== null && createPortal(
<p>Hello from React!</p>,
popupContainer
)}
</div>
);
这是你可以玩的完整示例:
¥Here is a complete example you can play with:
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> ); }