createPortal

createPortal 允许你将某些子元素渲染到 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>

查看更多示例。

门户(Portal)只改变 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 />)、一个 Fragment<>...</>)、一个字符串或数字,或者它们的数组。
  • domNode:某些 DOM 节点,例如由 document.getElementById() 返回的节点。该节点必须已经存在。在更新时传入不同的 DOM 节点会导致门户内容被重新创建。
  • 可选 key:作为门户密钥使用的唯一字符串或数字

返回

🌐 Returns

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

注意事项

🌐 Caveats

  • 来自门户的事件是根据 React 树而不是 DOM 树传播的。例如,如果你点击门户内部,并且该门户被 <div onClick> 封装,那么 onClick 的处理程序会触发。如果这导致问题,要么从门户内部阻止事件传播,要么将门户本身在 React 树中向上移动。

用法

🌐 Usage

渲染到 DOM 的不同部分

🌐 Rendering to a different part of the DOM

Portal 允许你的组件将其部分子元素渲染到 DOM 的不同位置。这使得组件的一部分可以从它所在的任何容器中“逃逸”。例如,一个组件可以显示一个模态对话框或一个工具提示,这些会出现在页面的其他部分之上和之外。

要创建一个门户,请将 createPortal 的结果与 一些 JSX 以及 它应该放置的 DOM 节点一起渲染:

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 节点中。

没有门户,第二个 <p> 会被放置在父级 <div> 内,但门户将它“传送”到了 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>

Portal 只是改变 DOM 节点的物理位置。在其他所有方面,你渲染到 portal 的 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>
    </>
  );
}

易犯错误

在使用门户时,确保你的应用可访问是很重要的。例如,你可能需要管理键盘焦点,以便用户可以以自然的方式在门户内外移动焦点。

🌐 It’s important to make sure that your app is accessible when using portals. For instance, you may need to manage keyboard focus so that the user can move the focus in and out of the portal in a natural way.

在创建模态框时,请遵循 WAI-ARIA 模态框编写规范。如果使用社区包,请确保其可访问并遵循这些指南。

🌐 Follow the WAI-ARIA Modal Authoring Practices when creating modals. If you use a community package, ensure that it is accessible and follows these guidelines.


将 React 组件渲染到非 React 服务器标记中

🌐 Rendering React components into non-React server markup

如果你的 React 根节点只是构建在一个静态或服务器渲染页面的一部分,而该页面不是用 React 构建的,Portals 可能会很有用。例如,如果你的页面是用像 Rails 这样的服务器框架构建的,你可以在静态区域(如侧边栏)内创建交互区域。与拥有多个独立的 React 根节点相比,portals 允许你将应用视为单一的 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

你也可以使用 portal 来管理由 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);
}
}, []);

这让你可以在 popupContainer 可用后使用 createPortal 将 React 内容渲染到其中:

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