レンダーとコミット
コンポーネントは、画面上に表示される前に React によってレンダーされる必要があります。このプロセスが踏む段階を理解すると、コードがどのように実行されるのか考える際や、コードの振る舞いを説明する際に役立ちます。
このページで学ぶこと
- React での「レンダー」の意味
- いつ、なぜ React はコンポーネントをレンダーするのか
- 画面上にコンポーネントが表示されるステップ
- レンダーしたからといって DOM が更新されるとは限らない理由
コンポーネントが料理人として厨房に立ち、食材を調理して美味しい料理を作っている様子をイメージしてみてください。このシナリオにおいて React はウェイターです。お客様の注文を伝えて、できた料理をお客様に渡します。この UI の「注文」と「提供」のプロセスは、次の 3 つのステップからなります:
- レンダーのトリガ(お客様の注文を厨房に伝える)
- コンポーネントのレンダー(厨房で注文の品を料理する)
- DOM へのコミット(テーブルに注文の品を提供する)
Illustrated by Rachel Lee Nabors
ステップ 1:レンダーのトリガ
コンポーネントがレンダーされる理由には 2 つあります。
- コンポーネントの初回レンダー。
- コンポーネント(またはその祖先のいずれか)の state の更新。
初回レンダー
アプリが開始するときには、初回のレンダーをトリガする必要があります。フレームワークやサンドボックスは、しばしばこのコードを隠蔽しますが、自力で行う場合には、ターゲットとなる DOM ノードに対して createRoot
を呼び出し、作成されたルートの render
メソッドを、コンポーネントに対して呼び出します。
import Image from './Image.js'; import { createRoot } from 'react-dom/client'; const root = createRoot(document.getElementById('root')) root.render(<Image />);
root.render()
の呼び出しをコメントアウトして、コンポーネントが消えるのを確認してみてください!
state 更新後の再レンダー
コンポーネントが最初にレンダーされた後、set
関数を使って state を更新することで、さらなるレンダーをトリガすることができます。コンポーネントの state を更新すると、自動的にレンダーがキューイングされます。(これは、レストランの客が最初の注文の後に、喉の渇きや空腹の状態に応じてお茶やデザートなどいろいろなものを注文するようなものだと考えることができます。)
Illustrated by Rachel Lee Nabors
ステップ 2:React がコンポーネントをレンダー
あなたがレンダーをトリガした後、React はコンポーネントを呼び出して画面に表示する内容を把握します。「レンダー」とは、React がコンポーネントを呼び出すことです。
- 初回レンダー時、React はルート (root) コンポーネントを呼び出します。
- 次回以降のレンダーでは、state の更新によってレンダーがトリガされた関数コンポーネントを、React が呼び出します。
このプロセスは再帰的に発生します。更新されたコンポーネントが他のコンポーネントを返す場合、次にそのコンポーネントを React がレンダーし、そのコンポーネントも何かコンポーネントを返す場合、そのコンポーネントも次にレンダーし、といった具合に続きます。このプロセスは、ネストされたコンポーネントがなくなり、React が画面に表示されるべき内容を知り尽くすまで続きます。
次の例では、React は Gallery()
を呼び出した後、Image()
を何度も呼び出します。
export default function Gallery() { return ( <section> <h1>Inspiring Sculptures</h1> <Image /> <Image /> <Image /> </section> ); } function Image() { return ( <img src="https://i.imgur.com/ZF6s192.jpg" alt="'Floralis Genérica' by Eduardo Catalano: a gigantic metallic flower sculpture with reflective petals" /> ); }
- 初回レンダー時には、React は
<section>
、<h1>
、および 3 つの<img>
タグの DOM ノードを作成します。 - 再レンダー時には、React は前回のレンダーからどの部分が変わったのか、あるいは変わらなかったのかを計算します。次のステップであるコミットフェーズまでこの情報は使われません。
さらに深く知る
更新されたコンポーネントがツリー内で非常に高い位置にある場合、その内部にネストされたすべてのコンポーネントを再レンダーするというデフォルトの挙動は、パフォーマンスにとって理想的ではありません。パフォーマンスの問題に遭遇した場合、パフォーマンスセクションで述べられているいくつかのオプトインによる解決方法があります。早まった最適化をしてしまってはいけません!
ステップ 3:React が DOM への変更をコミットする
あなたのコンポーネントをレンダー(関数として呼び出し)した後、React は DOM を変更します。
- 初回レンダー時には、React は
appendChild()
DOM API を使用して、作成したすべての DOM ノードを画面に表示します。 - 再レンダー時には、React は最新のレンダー出力に合わせて DOM を変更するため、必要な最小限の操作(レンダー中に計算されたもの!)を適用します。
React はレンダー間で違いがあった場合にのみ DOM ノードを変更します。例えば、以下のコンポーネントは親から渡された異なる props で毎秒再レンダーされます。実際に試してみてください。<input>
にテキストを追加して value
を更新することができますが、コンポーネントが再レンダーされる瞬間もテキストが消えることはありません:
export default function Clock({ time }) { return ( <> <h1>{time}</h1> <input /> </> ); }
これが上手く動作するのは、最終ステップで React によって更新されるのが、新しい time
の値で更新される <h1>
の中身だけだからです。<input>
は JSX 内で前回と同じ場所にあるので、React は <input>
やその value
に触れません!
エピローグ:ブラウザのペイント
レンダーが完了し、React が DOM を更新した後、ブラウザは画面を再描画します。このプロセスは「ブラウザレンダリング」として知られていますが、我々は、混乱を避けるために、ドキュメント全体を通して「ペイント」と呼ぶことにします。
Illustrated by Rachel Lee Nabors