【React】既存のページにReactを適用する【createPortal】

こんにちは、フリーランスエンジニアの太田雅昭です。

React

Reactは、インタラクティブなUIを比較的手軽に実装できるフレームワーク、厳密にはライブラリです。通常、1ページに1つマウントします。複数マウントするにはcreateRootを複数使用するなどもありますが、その場合は状態が共有されません。今回は、createPortalを使用して、状態を共有しつつ、複数のコンポーネントを既存のページにマウントしていきます。

createPortal

以下で解説されています。

https://ja.react.dev/reference/react-dom/createPortal

新しく追加する要素、既存の要素、キーの順に指定します。

<div>
  <SomeComponent />
  {createPortal(children, domNode, key?)}
</div>

既存の複数要素にマウントする

以下のようにします。

// index.html
// ...
 <h2>既存のページ</h2>
  <ul id="existing-list">
    <li class="some-class">アイテム1</li>
    <li class="some-class">アイテム2</li>
    <li class="some-class">アイテム3</li>
  </ul>

  <div id="root" style="border: 1px solid red"></div>
  <script type="module" src="/src/main.tsx"></script>
// ...
// App.tsx

import { useEffect, useState } from "react";
import { createPortal } from "react-dom";

const App = () => {
  const [targetLis, setTargetLis] = useState<HTMLElement[]>([]);
  const [count, setCount] = useState(0);

  useEffect(() => {
    const lis = Array.from(document.querySelectorAll("li.some-class")) as HTMLElement[];
    setTargetLis(lis);
  }, []);

  return <>
    <h2>React</h2>
    {targetLis.map((li) =>
      createPortal(
        <button
          onClick={() => setCount(count + 1)}
        >
          {count}
        </button>,
        li
      )
    )}
  </>
};

export default App;

Reactはid=rootにレンダリングしていますが、createPortalを使用した部分だけは、既存のリストに追加されています。ボタンを押すと、全体で共有される状態管理が適用され、すべての数字がインクリメントされます。これは便利。

想定される用途

今回、Chrome拡張で既存のページにReactを入れたくて調べました。思ったより簡単にできそうでよかったです。他にも、色々使い道はありそうです。Electronだとか、Bookmarkletだとか。うむ。