レンダーとコミット
コンポーネントは画面上に表示される前に、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