跳至主要内容

[ReactDoc] Portals

  • 內文資料來源:Portals @ React Docs

目的

透過 Portal,可以在 React root DOM node(一般是 #app) 以外的地方 DOM node 將 React 的內容 render 出來。同時,也能以解決 z-index 和 stacking context 導致 UI 被其他元素覆蓋的問題。

範例

HTML

雖然這裡建立了兩個 DOM,但透過 React.createPortal 可以把 #root 的 React children 在 #modal-root 中 render 出來;此外,雖然從 HTML 來看,#root#modal-root 是 sibling 的關係,但透過 React.createPortal,在 #modal-root 內所做的操作和行為(例如,event bubbling),就會像是在 #root 裡面一樣。

<!-- index.html -->
<body>
<div id="root"></div>
<div id="modal-root"></div>
</body>

React Component

ReactDOM.createPortal() 的 API 和 React 17 以前的 ReactDOM.render() 很像,第一個參數放的是 JSX,第二個參數則是要被 mounted 上去的 DOM 元素。

const Modal = ({ children }) => {
// STEP 1: 建立一個 div
const el = useRef(document.createElement('div'));

useEffect(() => {
// STEP 2: 找到 #modal-root
const modalRoot = document.getElementById('modal-root');

// STEP 3: 把 div 放到 #modal-root
modalRoot.appendChild(el.current);

return () => {
// STEP 5: 將 #modal-root 中的 div 移除
modalRoot.removeChild(el.current);
};
}, []);

console.log(el.current);

// STEP 4: 將 children 的內容放到 div 中
return ReactDOM.createPortal(children, el.current);
};

App

  • <Modal /> 中 children 的內容,會出現在 #modal-root
const DemoPortal = () => {
const [showModal, setShowModal] = useState(false);
const handleShow = () => {
setShowModal(true);
};
const handleHide = () => {
setShowModal(false);
};

return (
<div>
<button onClick={handleShow}>Show Modal</button>
{showModal && (
<Modal>
<div className="modal">
<div>
With a portal, we can render content into a different part of the DOM, as if it were
any other React child.
</div>
This is being rendered inside the #modal-container div.
<button onClick={handleHide}>Hide modal</button>
</div>
</Modal>
)}
</div>
);
};

Material UI Modal 的實作

Material UI Modal 的實作可以參考這幾隻檔案:

Material UI Modal 預設會使用 React.createPortal() 並把它 mount 在 document.body 上(可以從這裡的程式碼看到)。