UI をツリーとして理解する

React アプリは、多数のコンポーネントが互いにネストされることで形成されます。React はどのようにアプリのコンポーネント構造を管理しているのでしょうか?

React をはじめとする多くの UI ライブラリは、UI をツリーとしてモデル化します。アプリをツリーとして捉えることにより、コンポーネント間の関係を理解するのに役立ちます。これを理解することで、これから学んでいくパフォーマンスや state 管理に関連した問題をデバッグするのに役立つでしょう。

このページで学ぶこと

  • React にはコンポーネント構造がどのように「見える」のか
  • レンダーツリーとは何で、何の役に立つのか
  • モジュール依存ツリーとは何で、何の役に立つのか

UI をツリーとして理解する

ツリーとはアイテム間の関係を表すモデルの一種です。UI はよくツリー構造を使用して表現されます。例えば、ブラウザは HTML (DOM) や CSS (CSSOM) をモデル化するためにツリー構造を使用します。モバイルプラットフォームもビューの階層構造を表現するためにツリーを使用します。

横に並んだ3つのセクションからなる図。最初のセクションには、'Component A'、'Component B'、'Component C' とラベル付けされた 3 つの長方形が縦に積み重ねられている。次のパネルへの遷移は 'React' とラベル付けされた React ロゴが上にある矢印で示されている。中央セクションには、ルートが 'A' で子が 'B' と 'C' のラベルが付いたコンポーネントのツリーがある。次のセクションへの遷移も、'React' とラベル付けされた React ロゴが上にある矢印で示されている。最後である 3 つ目のセクションは、8 つのノードからなるツリーを含むブラウザのワイヤーフレームで、そのうちの一部だけが強調表示され、中央のセクションからのサブツリーを示している。
横に並んだ3つのセクションからなる図。最初のセクションには、'Component A'、'Component B'、'Component C' とラベル付けされた 3 つの長方形が縦に積み重ねられている。次のパネルへの遷移は 'React' とラベル付けされた React ロゴが上にある矢印で示されている。中央セクションには、ルートが 'A' で子が 'B' と 'C' のラベルが付いたコンポーネントのツリーがある。次のセクションへの遷移も、'React' とラベル付けされた React ロゴが上にある矢印で示されている。最後である 3 つ目のセクションは、8 つのノードからなるツリーを含むブラウザのワイヤーフレームで、そのうちの一部だけが強調表示され、中央のセクションからのサブツリーを示している。

React はコンポーネントから UI ツリーを作成する。この例では、UI ツリーは DOM へのレンダーに使用されている。

ブラウザやモバイルプラットフォームと同様に、React もツリー構造を使用して React アプリ内のコンポーネント間の関係を管理し、モデル化します。そのようなツリーは、React アプリ内をデータがどのように流れるか理解し、レンダーやアプリサイズを最適化する際の有用なツールとなります。

レンダーツリー

コンポーネントの主要な特徴のひとつは、コンポーネント同士を組み合わせられることです。コンポーネントをネストすることで、親コンポーネント・子コンポーネントという概念が発生します。その親コンポーネントもまた、別のコンポーネントの子かもしれません。

React アプリをレンダーする際、この関係性をツリーとしてモデル化することができます。これをレンダーツリーと呼びます。

以下は、ひらめきを与えてくれる格言をレンダーするための React アプリです。

import FancyText from './FancyText';
import InspirationGenerator from './InspirationGenerator';
import Copyright from './Copyright';

export default function App() {
  return (
    <>
      <FancyText title text="Get Inspired App" />
      <InspirationGenerator>
        <Copyright year={2004} />
      </InspirationGenerator>
    </>
  );
}

5 つのノードからなるツリー。各ノードはコンポーネントを表している。ツリーのルートは App で、それから矢印が 2 つ伸びており、'InspirationGenerator' と 'FancyText' を指している。矢印には 'renders' という言葉が書かれている。'InspirationGenerator' のノードからも矢印が 2 つ伸びており、'FancyText' と 'Copyright' のノードを指している。
5 つのノードからなるツリー。各ノードはコンポーネントを表している。ツリーのルートは App で、それから矢印が 2 つ伸びており、'InspirationGenerator' と 'FancyText' を指している。矢印には 'renders' という言葉が書かれている。'InspirationGenerator' のノードからも矢印が 2 つ伸びており、'FancyText' と 'Copyright' のノードを指している。

React は、レンダーされたコンポーネントから構成される UI ツリーであるレンダーツリーを作成する

このアプリから、上のようなレンダーツリーを構築することができます。

ツリー構造はノードで構成されており、各ノードがコンポーネントを表します。AppFancyTextCopyright などはすべてこのツリーのノードです。

React レンダーツリーのルートノードは、アプリのルートコンポーネントとなります。この場合、ルートコンポーネントは App であり、React が最初にレンダーするコンポーネントです。ツリーの各矢印は、親コンポーネントから子コンポーネントに伸びています。

さらに深く知る

レンダーツリー内の HTML タグはどこに?

上記のレンダーツリーの図には、各コンポーネントがレンダーする HTML タグについては載っていません。これは、レンダーツリーとは React のコンポーネントだけで構成されるものだからです。

UI フレームワークとしての React は特定のプラットフォームに依存しません。react.dev ではウェブへレンダーする例が紹介されており、そこでは UI のプリミティブとして HTML マークアップが使用されます。しかし、React アプリは同様にモバイルやデスクトッププラットフォームにレンダーすることも可能であり、そこでは UIViewFrameworkElement のような別の UI プリミティブが使用されるでしょう。

これらのプラットフォームの UI プリミティブは React の一部ではありません。React のレンダーツリーを考えることにより、アプリがどのプラットフォームにレンダーされるのかとは独立して、React アプリを理解できるようになります。

レンダーツリーは、React アプリケーションにおける 1 回のレンダーを表します。条件付きレンダーを使用することで、親コンポーネントは渡されたデータに応じて異なる子をレンダーすることができます。

アプリを更新して、格言とカラーのいずれかが条件付きでレンダーされるようにしてみましょう。

import FancyText from './FancyText';
import InspirationGenerator from './InspirationGenerator';
import Copyright from './Copyright';

export default function App() {
  return (
    <>
      <FancyText title text="Get Inspired App" />
      <InspirationGenerator>
        <Copyright year={2004} />
      </InspirationGenerator>
    </>
  );
}

6 つのノードからなるツリー。ツリーのルートは App で、それから矢印が 2 つ伸びており、'InspirationGenerator' と 'FancyText' を指している。矢印は実線であり 'renders' と書かれている。'InspirationGenerator' のノードからは矢印が 3 つ伸びている。そのうち 'FancyText' と 'Color' への矢印は点線であり 'renders?' と書かれている。もう 1 本の矢印は実線で 'Copyright' のノードを指しており、'renders' と書かれている。
6 つのノードからなるツリー。ツリーのルートは App で、それから矢印が 2 つ伸びており、'InspirationGenerator' と 'FancyText' を指している。矢印は実線であり 'renders' と書かれている。'InspirationGenerator' のノードからは矢印が 3 つ伸びている。そのうち 'FancyText' と 'Color' への矢印は点線であり 'renders?' と書かれている。もう 1 本の矢印は実線で 'Copyright' のノードを指しており、'renders' と書かれている。

条件付きレンダーにより、違うレンダーではレンダーツリーが異なるコンポーネントをレンダーする。

この例では、inspiration.type の値によって、<FancyText> または <Color> のいずれかがレンダーされます。一連のレンダーが起きるたびに、レンダーツリーは異なったものになる可能性があるのです。

毎回のレンダーごとにレンダーツリーが異なることがあるにせよ、このようなツリーは一般的に、React アプリケーションにおいてトップレベルコンポーネントとリーフ(葉, 末端) コンポーネントがどれなのかを理解するのに役立ちます。トップレベルコンポーネントとはルートコンポーネントに最も近いコンポーネントです。下にあるすべてのコンポーネントのレンダーパフォーマンスに影響を与え、しばしばとても複雑な内容を含んでいます。リーフコンポーネントはツリーの下側にあり、子コンポーネントを持たず、通常は頻繁に再レンダーされます。

これらのカテゴリのコンポーネントを特定することにより、アプリケーションのデータの流れとパフォーマンスを理解するのに役立ちます。

モジュール依存関係ツリー

React アプリにおいて、ツリー構造で関係性をモデル化できるものがもうひとつあります。アプリのモジュールの依存関係です。コンポーネントやロジックを別々のファイルに分割することで、JS モジュールを作成し、コンポーネントや関数や定数をエクスポートします。

モジュール依存関係ツリーにおいては、各ノードはモジュールとなり、それぞれの枝はそのモジュール内の import 文を表します。

先ほどのひらめきアプリの例では、以下のようなモジュール依存関係ツリー(あるいは単に依存関係ツリー)を作成することができます。

7 つのノードからなるツリー。それぞれのノードにモジュール名が書かれている。ツリーのトップレベルのノードには 'App.js' と書かれている。そこから 3 つの矢印が伸びており、'InspirationGenerator.js'、'FancyText.js'、'Copyright.js' を指している。矢印には 'imports' と書かれている。'InspirationGenerator.js' からも 3 つの矢印が伸びており、'FancyText.js'、'Color.js'、'inspirations.js' を指している。矢印には 'imports' と書かれている。
7 つのノードからなるツリー。それぞれのノードにモジュール名が書かれている。ツリーのトップレベルのノードには 'App.js' と書かれている。そこから 3 つの矢印が伸びており、'InspirationGenerator.js'、'FancyText.js'、'Copyright.js' を指している。矢印には 'imports' と書かれている。'InspirationGenerator.js' からも 3 つの矢印が伸びており、'FancyText.js'、'Color.js'、'inspirations.js' を指している。矢印には 'imports' と書かれている。

ひらめきアプリのモジュール依存関係ツリー

このツリーのルートノードはルートモジュールで、エントリーポイントファイルとも呼ばれます。これがルートコンポーネントを含んだモジュールであることも多いでしょう。

同じアプリのレンダーツリーと比べると、似た部分もありますが、いくつか注目すべき違いがあります。

  • ツリーを構成するノードが表しているのはコンポーネントではなくモジュールです。
  • コンポーネントの書かれていない inspirations.js のようなモジュールもこのツリーには含まれています。レンダーツリーはコンポーネントのみを含みます。
  • Copyright.jsApp.js の下に表示されていますが、レンダーツリーの方では、Copyright コンポーネントは InspirationGenerator の子でした。これは、InspirationGenerator が props である children 経由で JSX を受け付けるためです。Copyright を子コンポーネントとしてレンダーしてはいますが、それに対応するモジュールをインポートしているわけではありません。

依存関係ツリーは、React アプリを実行するためにどのモジュールが必要かを判断するのに役立ちます。通常、React アプリを本番環境用にビルドする際には、クライアントに送信するために必要な JavaScript をすべてバンドルにまとめるというビルドステップが存在します。これを担当するツールはバンドラと呼ばれ、バンドラは依存関係ツリーを使用することで、どのモジュールを含めるべきかを決定します。

アプリが成長するにつれて、バンドルサイズも大きくなります。バンドルサイズが大きいと、クライアントがダウンロードして実行するのにコストがかかります。バンドルサイズが大きいと、UI が描画されるまでの時間も遅くなります。アプリの依存関係ツリーを把握することで、これらの問題のデバッグに役立つでしょう。

まとめ

  • ツリー構造とは、何らかの物どうしの関係性を表現する際の一般的な方法である。UI をモデル化するために多用される。
  • レンダーツリーは、1 回のレンダーにおける React コンポーネント間のネスト関係を表現するものである。
  • 条件付きレンダーにより、毎回のレンダー間でレンダーツリーは変化する可能性がある。例えば props の値が変わることでコンポーネントは異なる子コンポーネントをレンダーする可能性がある。
  • レンダーツリーの概念は、トップレベルとリーフコンポーネントを特定するのに役立つ。トップレベルのコンポーネントはそれらの下の全コンポーネントのレンダーパフォーマンスに影響を与え、リーフコンポーネントは頻繁に再レンダーされる。これらを把握することでレンダーパフォーマンスの理解とデバッグに役立つ。
  • 依存関係ツリーは、React アプリ内のモジュール依存関係を表現する。
  • 依存関係ツリーは、アプリを届けるために必要なコードをバンドルするビルドツールによって使用される。
  • 依存関係ツリーは、ペイントまでの時間を遅らせるバンドルサイズの問題をデバッグしたり、どのコードをバンドル対象とするか最適化するきっかけとなることに役立つ。