createPortal

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

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

参考

createPortal(children, domNode, key?)

要创建一个portal,调用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>

请参见下面的更多示例。

portal只改变DOM节点的物理位置。在其他方面,你渲染到portal中的JSX充当渲染它的React组件的子节点。例如,子元素可以访问父树提供的上下文,事件根据React树从子元素冒泡到父元素。

参数

  • children:任何可以用React渲染的东西,例如一段JSX(例如 <div /><SomeComponent />),一个Fragment<>...</>),一个字符串或数字,或这些的数组。

  • domNode:一些DOM节点,例如 document.getElementById() 返回的那些节点。该节点必须已经存在。在更新期间传递不同的DOM节点将导致portal内容被重新创建。

  • 可选 key:一个唯一字符串或数字,用作portal的键。

返回

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

注意事项

  • 门户中的事件根据 React 树而不是 DOM 树传播。例如,如果您点击门户内部,并且门户被包裹在 <div onClick> 中,则该 onClick 处理程序将触发。如果这导致问题,请停止门户内的事件传播,或将门户本身在 React 树中向上移动。

用法

渲染到 DOM 的不同部分

门户允许您的组件将其一些子元素渲染到 DOM 中的不同位置。这允许您组件的一部分“逃离”它可能所在的任何容器。例如,组件可以显示一个模态对话框或一个出现在页面其余部分上方和外部的工具提示。

要创建一个门户,请使用 一些 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 节点 中。

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

门户只改变 DOM 节点的物理位置。在其他方面,您渲染到门户中的 JSX 充当渲染它的 React 组件的子节点。例如,子元素可以访问父树提供的上下文,事件仍然根据 React 树从子元素冒泡到父元素。


使用门户渲染模态对话框

您可以使用门户来创建一个模态对话框,该对话框浮动在页面的其余部分上方,即使调用该对话框的组件位于具有 overflow: hidden 或其他干扰对话框样式的容器中。

在此示例中,两个容器的样式会破坏模态对话框,但渲染到门户中的对话框不受影响,因为在 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>
    </>
  );
}

陷阱

使用门户时,务必确保您的应用程序可访问。例如,您可能需要管理键盘焦点,以便用户能够以自然的方式在门户内外移动焦点。

创建模态时,请遵循 WAI-ARIA 模态创作实践。如果您使用社区软件包,请确保它是可访问的并遵循这些准则。


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

如果您的 React 根目录只是静态或服务器渲染页面的一个部分,而该页面不是使用 React 构建的,则门户可能很有用。例如,如果您的页面是使用 Rails 等服务器框架构建的,则可以在静态区域(例如侧边栏)内创建交互区域。与 多个单独的 React 根目录 相比,门户允许您将应用程序视为具有共享状态的单个 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 节点

您还可以使用门户来管理在 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>
  );
}