React.jsポータル

独自のDOMツリーの外に要素をレンダリングする魔法

React.jsでのテレポート

Reactが「ポータル」と呼ばれる特別で非常に強力なコンポーネントを提供し、アプリ内のまったく別の場所に子をマウントできることをご存知ですか?そうでない場合は、何か新しいことを学ぶ時が来ました!

React.jsポータルの構造

これは、React.jsのPortalコンポーネントが以前にどのように機能していたかについての簡単な概要です。

  • コードのどこかに、ポータルの子をホストする要素をマウントします
  • 次に、この要素への参照を取得します
  • 次に、アプリの別の場所で、Portal-componentをマウントし、host-elementから取得した参照を渡します。
  • ポータル内にマウントされているものはすべて、最初のステップから実際にホストにマウントされます

何が起こっているのかをよりよく理解するために、ポータルの機能を説明するために次の簡単な視覚化を作成しました。

Image ee9c2dac28e7

Image 70ff8978589d

Image 886ed0a34985

これらの画像に基づいて、次の図は同じプロセスを示していますが、今回は簡略化されたコード例を使用しています。

Image dd655b015a22

Image ab94e9940e3a

ポータルは、当初考えていたよりもさらに強力です。複数のポータルの複数の子を同じターゲット要素にレンダリングすることもできます。ポータルのマウントの順序は、ホスト内の要素の順序も定義します。

Image de39ae9a02a2

Image b12fdf680f24

テレポートされたすべての子を含むホスト要素への参照を共有することも非常に簡単です。公式ドキュメントにはdocument.getElementByIdの使用法が記載されていますが、React.jsのuseRef-hookを使用することもできます。次のコード例は、これまでに説明したすべてを示す簡単な実装を示しています。

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

//
// Link to sandbox:
// 👉 https://codesandbox.io/s/react-16-8-0-forked-d8lm1?file=/src/index.js:0-1067
//

// A simple demonstration of the React-Portal
// w/ multiple portals for the same target.
function App() {
  const ref = useRef(null);
  const [isRefReady, setIsRefReady] = useState(false);

  // Run one more render to actually
  // show the content of our portal.
  // This is necessary, as the setter
  // for the 'ref' alone won't trigger
  // a render.
  useEffect(() => {
    setIsRefReady(Boolean(ref));
  }, []);

  return (
    <div>
      <div>
        {/* The 'target' aka host for our portals. */}
        <header ref={ref} style={{display: "flex", gap: 8 }} />
        <hr/>
      </div>
      <h1>
        Debug
      </h1>
      {isRefReady && createPortal(<button>one</button>, ref.current)}
      {isRefReady && createPortal(<button>two</button>, ref.current)}
      <div>
        {isRefReady && createPortal(<button>three</button>, ref.current)}
      </div>
    </div>
  );
}

ReactDOM.render(<App />, document.querySelector("#root"));

ポータルコンポーネントがより簡単なコーディングを可能にする方法

おそらく、React.jsポータルの最も一般的なユースケースは、モーダルコンポーネントの実装です。アプリのツリーのルートでターゲットを定義し、Portalコンポーネントを使用して、あらゆる場所からモーダルをマウントし、すべてのコンテンツの上に確実に浮かぶことができます。

私が実際に実装したもう1つのユースケースは、タブコンテナの使用法です。タブコンテナには、タブアイテム自体と、タブが選択されたときにレンダリングするコンテンツの両方が必要なため、タブ行用と選択されたコンテンツ用の2つのホストを定義するだけです。

次に、選択したコンテンツだけでなく、タブアイテムにもポータルを使用するコンポーネントの配列をマウントするだけです。これにより、ビジネスロジックを実際に属する場所(個別のコンポーネントごと)に配置できます。

Image 2761da74cf8c

Image e7810bb469d2

ポータルコンポーネントには、さらに興味深いユースケースが確かにあります。それが可能にすると思う最大の利点は、単一のコンポーネントでビジネスロジックが緊密に結合され、その子がさまざまな場所にマウントされることです。このパターンは、優れた柔軟性とカスタマイズを可能にしますが、ビューが実際にどのように関連しているかをコードベースで混乱させる可能性があります。

React.jsポータルに関するすべての詳細については、公式ドキュメントもご覧ください。