コンポーネントは、画面上に表示される前に React によってレンダーされる必要があります。このプロセスが踏む段階を理解すると、コードがどのように実行されるのか考える際や、コードの振る舞いを説明する際に役立ちます。

このページで学ぶこと

  • React での「レンダー」の意味
  • いつ、なぜ React はコンポーネントをレンダーするのか
  • 画面上にコンポーネントが表示されるステップ
  • レンダーしたからといって DOM が更新されるとは限らない理由

コンポーネントが料理人として厨房に立ち、食材を調理して美味しい料理を作っている様子をイメージしてみてください。このシナリオにおいて React はウェイターです。お客様の注文を伝えて、できた料理をお客様に渡します。この UI の「注文」と「提供」のプロセスは、次の 3 つのステップからなります:

  1. レンダーのトリガ(お客様の注文を厨房に伝える)
  2. コンポーネントのレンダー(厨房で注文の品を料理する)
  3. DOM へのコミット(テーブルに注文の品を提供する)
  1. レストランのウェイター役の React が、ユーザから注文を聞き取って、コンポーネントの厨房に渡している。
    トリガ
  2. Card シェフが React に出来立ての Card コンポーネントを渡している。
    レンダー
  3. React がユーザの座っているテーブルに Card を提供している。
    コミット

Illustrated by Rachel Lee Nabors

ステップ 1:レンダーのトリガ

コンポーネントがレンダーされる理由には 2 つあります。

  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 を更新すると、自動的にレンダーがキューイングされます。(これは、レストランの客が最初の注文の後に、喉の渇きや空腹の状態に応じてお茶やデザートなどいろいろなものを注文するようなものだと考えることができます。)

  1. レストランのウェイター役の React が Card UI を提供したところ。矢印頭の顧客は、黒ではなく、ピンクのカードが欲しいのだと注文している。
    state の更新が...
  2. React はコンポーネントの厨房に戻り、ピンクの Card が必要だと Card シェフに伝える。
    ... トリガになって ...
  3. Card シェフはピンクのカードを React に渡している。
    ... レンダーされる!

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 は前回のレンダーからどの部分が変わったのか、あるいは変わらなかったのかを計算します。次のステップであるコミットフェーズまでこの情報は使われません。

落とし穴

レンダーは常に純粋な計算であるべきです。

  • 同じ入力には同じ出力。同じ入力が与えられた場合、コンポーネントは常に同じ JSX を返す必要がある。(トマトサラダを注文した人がオニオンサラダを受け取ってはいけない!)
  • 自分の仕事に専念する。レンダー前に存在したオブジェクトや変数を変更しない。(ある注文が他の誰かの注文を変更してはいけない。)

これを守らなかった場合、コードベースが複雑になるにつれて、ややこしいバグや予測不能な挙動に遭遇することになります。“Strict Mode” で開発している場合、React は各コンポーネントの関数を 2 回呼び出すことで、純粋でない関数が引き起こす間違いに気づきやすくしてくれます。

さらに深く知る

パフォーマンスの最適化

更新されたコンポーネントがツリー内で非常に高い位置にある場合、その内部にネストされたすべてのコンポーネントを再レンダーするというデフォルトの挙動は、パフォーマンスにとって理想的ではありません。パフォーマンスの問題に遭遇した場合、パフォーマンスセクションで述べられているいくつかのオプトインによる解決方法があります。早まった最適化をしてしまってはいけません!

ステップ 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

まとめ

  • React アプリでの画面更新は、以下の 3 つのステップで行われる:
    1. トリガ
    2. レンダー
    3. コミット
  • Strict Mode を使って、コンポーネントの間違いを発見できる。
  • レンダー結果が前回と同一である場合、React は DOM を触らない。