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