【DOMを超越せよ】React Portal でモーダルやツールチップを「親の束縛」から解放し、配置を自由にする技術
97日目に「ヘッドレスUI」で機能の抽象化を学びました。本日98日目は、Reactにおける「DOMの階層構造」という制限を突破する最強のギミック、「React Portals」 を学習します。
モーダルやツールチップを作っていて、「親要素の overflow: hidden で表示が切れる!」「z-index が効かなくて困る!」と頭を抱えたことはありませんか?その苦しみ、Portalsがすべて解決します!
はじめに:なぜ「DOMツリー」はときに邪魔なのか?
Reactのコンポーネントは、コードに書いた通りにDOMツリー上に親子関係を作ります。しかし、UIの世界では「コード上の論理階層」と「視覚的な配置」が一致しないことがあります。
例えば、深いネストの中にいるコンポーネントから「画面全体を覆うモーダル」を出すとき、親のCSS(overflow: hidden や opacity)の影響をダイレクトに受けてしまうと、モーダルがズレたり隠れたりします。
React Portals は、コンポーネントの書き場所は維持したまま、実際のDOM要素を <body> 直下など、任意の場所にレンダリングさせるための機能です。
💡 1. Portals の仕組み
ReactDOM.createPortal(child, container) を使います。
第1引数にレンダリングしたい内容(JSX)、第2引数に配置したいDOMノードを指定します。
import ReactDOM from 'react-dom';
const Modal = ({ children }) => {
// 💡 body 直下の #modal-root という div に飛ばす
return ReactDOM.createPortal(
<div className="modal-overlay">{children}</div>,
document.getElementById('modal-root') // 事前にindex.htmlに作っておく
);
};
💡 2. なぜ「イベント伝播」は維持されるのか?
Portals のすごいところは、物理的に離れた場所にレンダリングされているのに、Reactのイベントシステム上は「親コンポーネントの直下」にいると見なされる点です。
つまり、Portalで飛ばしたモーダル内のボタンを押したとき、そのモーダルの親(Portalを使っているコンポーネント)で発生したイベントハンドラーは正常に呼び出されます。物理的なDOM階層を超えて、論理的なReact階層が維持されているからです。
💡 3. こんな時に Portals を使う
モーダル/ダイアログ: 画面全体を覆う必要があるため。
ツールチップ/ポップオーバー: 親の overflow: hidden から抜け出し、画面端まで表示させたいとき。
ドラッグ&ドロップ: 要素をリストから切り離してマウスに追従させるとき。
📝 98日目のまとめと課題
✅ 本日の習得ポイント
DOM階層の脱出: createPortal で、親のCSS制約を受けないレンダリングを行う。
イベントシステムの継承: 物理的に離れていても、論理的な親子関係をReactが維持してくれる。
配置の自由化: デザイン上の「重なり」や「はみ出し」問題を、設計レベルで解決する。
📝 98日目の課題
index.html に <div id="modal-root"></div> を追加し、モーダルコンポーネントを Portal を使ってそこに飛ばしてみましょう。
Portal の中にあるボタンから、その親のコンポーネント(Portalを呼んでいる親)のステートを更新できるかテストし、「論理階層が繋がっていること」を体験してみてください。
