React + TypeScript: レンダーとコミット

React公式サイトのドキュメントが2023年3月16日に改訂されました(「[Introducing react.dev](https://react.dev/blog/2023/03/16/introducing-react-dev)」参照)。本稿は、基本解説の「[Render and Commit](https://react.dev/learn/render-and-commit)」をかいつまんでまとめた記事です。ただし、コードにはTypeScriptを加えました。反面、初心者向けのJavaScriptの基礎的な説明は省いています。

なお、本シリーズ解説の他の記事については「[React + TypeScript: React公式ドキュメントの基本解説『Learn React』を学ぶ](https://qiita.com/FumioNonaka/items/d70585bd3c0a6d4edc24)」をご参照ください。

コンポーネントは画面に表示される前に、Reactがレンダリングしなければなりません。コンポーネントがレストランの料理人だとしましょう。すると、Reactは受けた注文にもとづき、客に料理を運ぶウェイターの役割です。

1. **レンダーを起動**: 客の注文を厨房に届けます。
2. **コンポーネントをレンダリング**: 厨房で料理をつくりあげる仕事です。
3. **DOMにコミット**: 料理を客のテーブルに運びます。

![calling_waiter.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128949/c2c4f72f-82e4-e98d-5302-72095252ce71.png)

3つの段階を順に見ていきましょう。


# レンダーを起動する
レンダーを起動しなければならないのは、つぎのふたつの場合です。

* コンポーネントの**初期レンダー**。
* コンポーネント(あるいは親)の**状態の更新による再レンダー**。

## 初期レンダー

アプリケーションが立ち上がったら、最初のレンダーを起動しなければなりません。フレームワークやサンドボックスでは、つぎのような立ち上げのコードは隠されていることもあります。それでも、対象のDOMノードに対して[`createRoot`](https://beta.reactjs.org/apis/react-dom/client/createRoot)が呼び出されて、`render`メソッドにより、初期のレンダーは行われるのです。

```react:src/index.tsx
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import App from './App';

const rootElement = document.getElementById('root');
const root = createRoot(rootElement!);
root.render(
	<StrictMode>
		<App />
	</StrictMode>
);
```

## 状態の更新による再レンダー
コンポーネントの初期レンダーが済んだあと、さらにレンダリングを引き起こすのは、[状態の設定関数](https://beta.reactjs.org/reference/react/useState#setstate)による更新です。コンポーネントの状態を更新すると、自動的にレンダリングがキューに加わります(レストランでいえば追加注文です)。


# コンポーネントをレンダーする

レンダリングが起動すると、Reactはコンポーネントの呼び出しにより、画面に何を表示するか決めます。「レンダリング」は、Reactがコンポーネントを呼び出すことです。

Reactは、つぎのようにコンポーネントを呼び出します。

* **初期**: ルートコンポーネントを呼び出す。
* **次回以降**: 状態の更新が実行された関数コンポーネントを呼び出す。

コンポーネントのレンダリングは再帰的です。更新されたコンポーネントが別のコンポーネントを返せば、Reactはつぎにそのレンダリングを行います。さらに戻り値があれば、レンダリングに加わえられるのです。ネストされたコンポーネントがなくなるまで、再帰的レンダリングは行われます。そうして、Reactは画面に何を表示すべきかを正確に把握するのです。

Reactは、つぎのようにレンダリングします。

* **初期**: 戻り値のJSXにしたがってつくるのが[DOMノード](https://developer.mozilla.org/docs/Web/API/Document/createElement)です。
* **次回以降**: 前回のレンダリングから	変更されたプロパティがあれば計算します。ただし、つぎのコミットの段階までは、その情報を使って何もしません。

レンダリングはつねに[純粋な計算](https://beta.reactjs.org/learn/keeping-components-pure)です。

* **入力が同じなら出力も同じでなければなりません**。 同じ入力が与えられれば、コンポーネントはつねに同じJSXを返すのです。
* **コンポーネントはそのコンポーネントの処理のみ行います**。レンダリング前にあったオブジェクトや変数を変更してはいけません。

そうしないと、コードが込み入ってくるにつれ、わかりにくいバグや予期しない動作に悩まされることになるでしょう。[`StrictMode`](https://ja.reactjs.org/docs/strict-mode.html)で開発すると、Reactはコンポーネントの関数を2度呼び出します。関数が純粋でないことにより起こる問題を明らかにするためです(「[React + TypeScript: React 18でコンポーネントのマウント時にuseEffectが2度実行されてしまう](https://qiita.com/FumioNonaka/items/f89443bc056eec5d8829)」参照)。


# DOMに変更をコミットする
コンポーネントのレンダリング(呼び出し)が済むと、ReactはDOMを変更します。

* **初期**: ReactはDOM APIの[`appendChild()`](https://developer.mozilla.org/docs/Web/API/Node/appendChild)により、すべてのDOMノードを画面に配置するのです。
* **次回以降**: Reactは最小限の操作(計算はレンダリング中)のみ加えて、DOMを最新のレンダリング出力に合わせます。

**Reactが変更するのは、レンダリングの間に変わったDOMノードだけです**。ひとつのコンポーネントが返す同じJSXの中でも、前のレンダリングから変わっていないノードには触れません。

レンダリングが済むとReactはDOMを更新します。そのうえで、ブラウザが画面を描き替える(repaint)のです。これは、一般には「ブラウザによるレンダリング」と称されます。けれど、Reactのレンダリングと区別するため、ドキュメントをとおして「描き替え(repaint)」と呼ぶことにしましょう。


# まとめ
この記事では、つぎのような項目についてご説明しました。

* Reactアプリケーションが画面を更新する手順はつぎの3段階です。
    - レンダーの起動。
    - レンダリング。
    - DOMへのコミット。
* `StrictMode`を使うと、関数が純粋でないことによる問題が確かめられます。
* Reactは、レンダリングの結果が前と変わらなければ、DOMには触れません。


***

***