React Portalsの活用(Modal作り)
React PortalsはReactコンポーネントをレンダリングする時、UIをどこにレンダリングするかについてDOMを事前に選んで親コンポーネントの外側にもレンダリングすることができるようにする機能です。
v16で導入されてちょっと時間が経ちましたが(現在はv17)、なかなか使う機がなくて今回を機に頭に整理しながら使ってみたいと思います。
基本
今まではchildren
は親コンポーネントのDOM内部にレンダリングする必要がありました。
const Wrapper = ({ children }) => {
return <div>{children}</div>;
}
Portalsを使ったら、DOM階層構造に関わらずコンポーネントをレンダリングすることができます。
const Portal = ({ children }) => {
const el = document.getElementById('portal');
return ReactDOM.createPortal(children, el);
}
どこかで見た気がするコードですね。はいエントリーファイル(src/index.js
)を見ると以下のコードの確認ができます。
ReactDOM.render(<App />, document.getElementById('root'));
document.getElementById
を使って、idがroot
のDOMを探して、Appコンポーネントにレンダリングするコードです。そのDOMはpublic/index.html
から確認できます。
public/index.html
<body>
<noscript>
You need to enable JavaScript to run this app.
</noscript>
<div id="root"></div>
</body>
活用
Portalsの活用ができる良い例としてはModal(モーダル)があります。その理由に関しては後で説明します。それではさっそく作ってみます。
まず、ModalのためのModalPortal
コンポーネントを作成します。
public/index.html
<body>
(...省略)
<div id="root"></div>
<div id="modal"></div>
</body>
src/ModalPortal.js
import ReactDOM from 'react-dom';
const ModalPortal = ({ children }) => {
const el = document.getElementById('modal');
return ReactDOM.createPortal(children, el);
};
export default ModalPortal;
次にModal
コンポーネントを作成します。
src/Modal.css
.modal {
background: rgba(0, 0, 0, 0.25);
position: fixed;
left: 0;
top: 0;
height: 100%;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
}
.modal .content {
background: white;
padding: 1rem;
width: 400px;
height: auto;
}
src/Modal.js
import './Modal.css';
const Modal = () => {
return (
<div className="modal">
<div className="content">
<h3>Modal</h3>
<p>This is Content</p>
<button>Close</button>
</div>
</div>
);
};
export default Modal;
src/App.css
.App {
text-align: center;
color: #61dafb;
}
src/App.js
import "./App.css";
import Modal from "./Modal";
export default function App() {
return (
<div className="App">
<h1>Hello World</h1>
<Modal />
</div>
);
}
Modal
コンポーネントがApp
コンポーネントの内部にレンダリングされて、App
コンポーネントのスタイルが適用されたのが確認できます。
続いて、ModalPortal
コンポーネントでModal
コンポーネントを包み込んでみます。
src/App.js
import "./App.css";
import Modal from "./Modal";
import ModalPortal from "./ModalPortal";
export default function App() {
return (
<div className="App">
<h1>Hello World</h1>
<ModalPortal>
<Modal />
</ModalPortal>
</div>
);
}
Modal
コンポーネントがApp
コンポーネントの外側(<div id="modal"></div>
)にレンダリングされ、App
コンポーネントのスタイルが解除されたのが確認できます。
このようにPortalを使ったら、不要なスタイルが適用されてしまう問題の対応やエレメントのレイヤー位置を決めるz-index等のスタイルを管理する時に役に立ちます。
終わりに
Portalsを使ったら、希望するDOMにレンダリングすることができます。Appコンポーネント以外にも構いません。Modalのようなコンポーネントを作る時、スタイルに関わる問題にぶつかったらPortalsを考慮してみることも良いと思います。
Discussion