異なるDOMノードにレンダリングする2つの React Component 間で状態を共有する
結論
解説
既存のサイトを部分的に React に置き換えている場合、それぞれ異なるDOMノードにレンダリングするコンポーネント間で状態を共有したい場面に遭遇することがあります。
例えば、次のようなヘッダー、サイドバー、コンテンツを表示するサイトを想像してみてください。
<html> <body> <div id="root"></div> <header class="header"></header> <aside class="sidebar"></aside> <main class="content"></main> </body> </html
このサイトのヘッダーとサイドバー部分を React で置き換えているとします。
ヘッダーにはサイドバーの表示/非表示を切り替えるボタンを設置し、クリックするたびにサイドバーが開閉します。
このとき、別々のDOMノードにコンポーネントをレンダリングしようとすると以下のようになります。
ReactDOM.render(<Header />, document.querySelector(".header")); ReactDOM.render(<Sidebar />, document.querySelector(".sidebar"));
ただし、ReactDom.render() で別々のDOMノードにレンダリングすると、Header と Sidebar コンポーネント間でサイドバーの開閉状態を共有することができません。
そこで使用するのが React の Portal です。
Portalを使えば、Virtual DOM 上では通常の親-子コンポーネントと同じように props を渡すことができて、実DOM上では親コンポーネント外の箇所に子コンポーネントをレンダリングすることができます。
使い方は ReactDOM.createPortal
の第1引数にコンポーネント、第2引数にDOMノードを指定するだけです。
const Index = () => { const [isOpenSidebar, setOpenSidebar] = useState(false); const headerPortal = ReactDOM.createPortal( <Header setOpenSidebar={setOpenSiebar} />, document.querySelector(".header") ); const sidebarPortal = ReactDOM.createPortal( <Sidebar isOpenSidebar={isOpenSidebar} />, document.querySelector(".sidebar") ); return ( <> {headerPortal} {sidebarPortal} </> ); }; ReactDOM.render(<Index />, document.getElementById("root")); // 任意のDOMノード