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 的 key。

回傳值

createPortal 會回傳一個可以包含在 JSX 中或從 React 元件回傳的 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 組件的子節點相同。例如,子節點可以存取父樹狀結構提供的 context,事件仍然會根據 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>
    </>
  );
}

陷阱

使用 Portal 時,確保您的應用程式易於存取非常重要。例如,您可能需要管理鍵盤焦點,以便使用者可以自然地將焦點移入和移出 Portal。

建立模態時,請遵循 WAI-ARIA 模態製作實務。如果您使用社群套件,請確保它易於存取並遵循這些準則。


將 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 地圖 widget 整合,並且您想要在地圖彈出視窗內渲染 React 內容。要做到這一點,請宣告一個 `popupContainer` 狀態變數來儲存您要渲染到的 DOM 節點。

const [popupContainer, setPopupContainer] = useState(null);

當您建立第三方 widget 時,請儲存 widget 傳回的 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>
  );
}