title |
---|
UI をツリーとして理解する |
React アプリは、多数のコンポーネントが互いにネストされることで形成されます。React はどのようにアプリのコンポーネント構造を管理しているのでしょうか?
React をはじめとする多くの UI ライブラリは、UI をツリーとしてモデル化します。アプリをツリーとして捉えることにより、コンポーネント間の関係を理解するのに役立ちます。これを理解することで、これから学んでいくパフォーマンスや state 管理に関連した問題をデバッグするのに役立つでしょう。
- React にはコンポーネント構造がどのように「見える」のか
- レンダーツリーとは何で、何の役に立つのか
- モジュール依存ツリーとは何で、何の役に立つのか
ツリーとはアイテム間の関係を表すモデルの一種です。UI はよくツリー構造を使用して表現されます。例えば、ブラウザは HTML (DOM) や CSS (CSSOM) をモデル化するためにツリー構造を使用します。モバイルプラットフォームもビューの階層構造を表現するためにツリーを使用します。
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>
</>
);
}
export default function FancyText({title, text}) {
return title
? <h1 className='fancy title'>{text}</h1>
: <h3 className='fancy cursive'>{text}</h3>
}
import * as React from 'react';
import quotes from './quotes';
import FancyText from './FancyText';
export default function InspirationGenerator({children}) {
const [index, setIndex] = React.useState(0);
const quote = quotes[index];
const next = () => setIndex((index + 1) % quotes.length);
return (
<>
<p>Your inspirational quote is:</p>
<FancyText text={quote} />
<button onClick={next}>Inspire me again</button>
{children}
</>
);
}
export default function Copyright({year}) {
return <p className='small'>©️ {year}</p>;
}
export default [
"Don’t let yesterday take up too much of today.” — Will Rogers",
"Ambition is putting a ladder against the sky.",
"A joy that's shared is a joy made double.",
];
.fancy {
font-family: 'Georgia';
}
.title {
color: #007AA3;
text-decoration: underline;
}
.cursive {
font-style: italic;
}
.small {
font-size: 10px;
}
React は、レンダーされたコンポーネントから構成される UI ツリーであるレンダーツリーを作成する
このアプリから、上のようなレンダーツリーを構築することができます。
ツリー構造はノードで構成されており、各ノードがコンポーネントを表します。App
、FancyText
、Copyright
などはすべてこのツリーのノードです。
React レンダーツリーのルートノードは、アプリのルートコンポーネントとなります。この場合、ルートコンポーネントは App
であり、React が最初にレンダーするコンポーネントです。ツリーの各矢印は、親コンポーネントから子コンポーネントに伸びています。
上記のレンダーツリーの図には、各コンポーネントがレンダーする HTML タグについては載っていません。これは、レンダーツリーとは React のコンポーネントだけで構成されるものだからです。
UI フレームワークとしての React は特定のプラットフォームに依存しません。react.dev ではウェブへレンダーする例が紹介されており、そこでは UI のプリミティブとして HTML マークアップが使用されます。しかし、React アプリは同様にモバイルやデスクトッププラットフォームにレンダーすることも可能であり、そこでは UIView や FrameworkElement のような別の 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>
</>
);
}
export default function FancyText({title, text}) {
return title
? <h1 className='fancy title'>{text}</h1>
: <h3 className='fancy cursive'>{text}</h3>
}
export default function Color({value}) {
return <div className="colorbox" style={{backgroundColor: value}} />
}
import * as React from 'react';
import inspirations from './inspirations';
import FancyText from './FancyText';
import Color from './Color';
export default function InspirationGenerator({children}) {
const [index, setIndex] = React.useState(0);
const inspiration = inspirations[index];
const next = () => setIndex((index + 1) % inspirations.length);
return (
<>
<p>Your inspirational {inspiration.type} is:</p>
{inspiration.type === 'quote'
? <FancyText text={inspiration.value} />
: <Color value={inspiration.value} />}
<button onClick={next}>Inspire me again</button>
{children}
</>
);
}
export default function Copyright({year}) {
return <p className='small'>©️ {year}</p>;
}
export default [
{type: 'quote', value: "Don’t let yesterday take up too much of today.” — Will Rogers"},
{type: 'color', value: "#B73636"},
{type: 'quote', value: "Ambition is putting a ladder against the sky."},
{type: 'color', value: "#256266"},
{type: 'quote', value: "A joy that's shared is a joy made double."},
{type: 'color', value: "#F9F2B4"},
];
.fancy {
font-family: 'Georgia';
}
.title {
color: #007AA3;
text-decoration: underline;
}
.cursive {
font-style: italic;
}
.small {
font-size: 10px;
}
.colorbox {
height: 100px;
width: 100px;
margin: 8px;
}
条件付きレンダーにより、違うレンダーではレンダーツリーが異なるコンポーネントをレンダーする。
この例では、inspiration.type
の値によって、<FancyText>
または <Color>
のいずれかがレンダーされます。一連のレンダーが起きるたびに、レンダーツリーは異なったものになる可能性があるのです。
毎回のレンダーごとにレンダーツリーが異なることがあるにせよ、このようなツリーは一般的に、React アプリケーションにおいてトップレベルコンポーネントとリーフ(葉, 末端) コンポーネントがどれなのかを理解するのに役立ちます。トップレベルコンポーネントとはルートコンポーネントに最も近いコンポーネントです。下にあるすべてのコンポーネントのレンダーパフォーマンスに影響を与え、しばしばとても複雑な内容を含んでいます。リーフコンポーネントはツリーの下側にあり、子コンポーネントを持たず、通常は頻繁に再レンダーされます。
これらのカテゴリのコンポーネントを特定することにより、アプリケーションのデータの流れとパフォーマンスを理解するのに役立ちます。
React アプリにおいて、ツリー構造で関係性をモデル化できるものがもうひとつあります。アプリのモジュールの依存関係です。コンポーネントやロジックを別々のファイルに分割することで、JS モジュールを作成し、コンポーネントや関数や定数をエクスポートします。
モジュール依存関係ツリーにおいては、各ノードはモジュールとなり、それぞれの枝はそのモジュール内の import
文を表します。
先ほどのひらめきアプリの例では、以下のようなモジュール依存関係ツリー(あるいは単に依存関係ツリー)を作成することができます。
ひらめきアプリのモジュール依存関係ツリー
このツリーのルートノードはルートモジュールで、エントリーポイントファイルとも呼ばれます。これがルートコンポーネントを含んだモジュールであることも多いでしょう。
同じアプリのレンダーツリーと比べると、似た部分もありますが、いくつか注目すべき違いがあります。
- ツリーを構成するノードが表しているのはコンポーネントではなくモジュールです。
- コンポーネントの書かれていない
inspirations.js
のようなモジュールもこのツリーには含まれています。レンダーツリーはコンポーネントのみを含みます。 Copyright.js
はApp.js
の下に表示されていますが、レンダーツリーの方では、Copyright
コンポーネントはInspirationGenerator
の子でした。これは、InspirationGenerator
が props である children 経由で JSX を受け付けるためです。Copyright
を子コンポーネントとしてレンダーしてはいますが、それに対応するモジュールをインポートしているわけではありません。
依存関係ツリーは、React アプリを実行するためにどのモジュールが必要かを判断するのに役立ちます。通常、React アプリを本番環境用にビルドする際には、クライアントに送信するために必要な JavaScript をすべてバンドルにまとめるというビルドステップが存在します。これを担当するツールはバンドラと呼ばれ、バンドラは依存関係ツリーを使用することで、どのモジュールを含めるべきかを決定します。
アプリが成長するにつれて、バンドルサイズも大きくなります。バンドルサイズが大きいと、クライアントがダウンロードして実行するのにコストがかかります。バンドルサイズが大きいと、UI が描画されるまでの時間も遅くなります。アプリの依存関係ツリーを把握することで、これらの問題のデバッグに役立つでしょう。
- ツリー構造とは、何らかの物どうしの関係性を表現する際の一般的な方法である。UI をモデル化するために多用される。
- レンダーツリーは、1 回のレンダーにおける React コンポーネント間のネスト関係を表現するものである。
- 条件付きレンダーにより、毎回のレンダー間でレンダーツリーは変化する可能性がある。例えば props の値が変わることでコンポーネントは異なる子コンポーネントをレンダーする可能性がある。
- レンダーツリーの概念は、トップレベルとリーフコンポーネントを特定するのに役立つ。トップレベルのコンポーネントはそれらの下の全コンポーネントのレンダーパフォーマンスに影響を与え、リーフコンポーネントは頻繁に再レンダーされる。これらを把握することでレンダーパフォーマンスの理解とデバッグに役立つ。
- 依存関係ツリーは、React アプリ内のモジュール依存関係を表現する。
- 依存関係ツリーは、アプリを届けるために必要なコードをバンドルするビルドツールによって使用される。
- 依存関係ツリーは、描画までの時間を遅らせるバンドルサイズの問題をデバッグしたり、どのコードをバンドル対象とするか最適化するきっかけとなることに役立つ。