2.5 React が自動化していること
ここまでの 2.2 / 2.3 / 2.4 で、ToDo リストを素の JavaScript で実装してきました。全消去式から差分更新式に書き換え、さらに「同期バグ」の例を見ました。
この章では、そこで書いた仕事と、つまずいたポイントを整理します。それぞれが React のどの機能に対応するのかを先取りするのが目的です。Part 3 以降で新しい用語を見たときに、「ああ、あの面倒の代替だ」と位置づけられるようにしておきましょう。
2.5.1 手書きで書いた仕事の一覧
Part 2 を通じて、次の 5 つを自分の手で書きました。
| 手書きした仕事 | 登場した章 | 何を書いたか |
|---|---|---|
| 1. 状態 → DOM のマッピング | 2.2 / 2.3 | render 関数の中で todos 配列を走査し、li を作る |
| 2. 差分計算 | 2.3 | 既存 li と新しい todos を id で突き合わせて挿入・更新・削除 |
| 3. イベントハンドラの寿命管理 | 2.3 / 2.4 | 作成時に 1 度だけ登録、更新時は登録しない |
| 4. 状態更新後の再描画トリガ | 2.2 / 2.4 | 状態を変えた各所で render() を呼ぶ |
| 5. 安全なテキスト挿入 | 2.4 | innerHTML ではなく textContent / createElement を使う |
これらは 1 行コードではなく、ルールや習慣の集合です。ルールを破ると同期バグに直結します。ここがしんどいところですね。
2.5.2 それぞれが React のどの機能に置き換わるか
上の 5 つが React では何に対応するのかを見ていきます。各項目は Part 3 以降で詳しく扱うので、ここではリンクと 1〜2 文の要約に留めます。
(1) 状態 → DOM のマッピング → JSX
render 関数の中で createElement("li") を延々と呼んでいた箇所は、React では JSX で書きます。
// React の場合(Part 3 で登場)
function TodoItem({ todo, onToggle, onDelete }) {
return (
<li className={todo.completed ? "completed" : ""}>
<input type="checkbox" checked={todo.completed} onChange={onToggle} />
<span>{todo.text}</span>
<button onClick={onDelete}>x</button>
</li>
);
}見た目は HTML に似ていますが、実体は React.createElement(...) への関数呼び出しに変換されます。DOM を作る命令を自分で書かず、「UI はこうあるべき」という宣言 を書くだけにします。詳細は 3.2 JSX の本質 で扱います。
(2) 差分計算 → Virtual DOM と Reconciliation
2.3 で手書きした「既存要素との突き合わせ」は、React では内部の Virtual DOM と Reconciliation が担当します。
- 開発者はレンダーのたびに「今こう見えるべき」を返すだけ
- React は前回の結果と今回の結果を比較し、差分だけを実 DOM に反映する
React 公式 legacy ブログ(2013)は、この考え方を次のように説明しています。
The data returned from render is neither a string nor a DOM node — it's a lightweight description of what the DOM should look like. (render が返すのは文字列でも DOM ノードでもない。DOM がどうあるべきかの軽量な記述である)
差分計算はアルゴリズムと内部データ構造に踏み込むテーマです。概念的な骨格は 6.1 Virtual DOM のアイデア と 6.2 Reconciliation と Fiber で扱います。
なお、リスト描画で「どの要素を同じとみなすか」は React でも自分で伝える必要があります。2.3 で li.dataset.id に todo.id を書き込んだのと同じ発想が、React では key プロパティに対応します。詳細は 3.7 リストと key — なぜ必要か にあります。
(3) イベントハンドラの寿命管理 → 合成イベントと宣言的登録
React では <button onClick={handleClick}> と JSX に書くだけで済み、addEventListener と removeEventListener のペアを自分で揃える必要はありません。ハンドラの登録・解除は React が要素の生成・破棄に合わせて自動で行います。
React 内部ではイベントは 合成イベント(SyntheticEvent) としてルートで 1 度だけ受け、適切なハンドラへ振り分けます。これは Part 4 / Part 6 で触れますが、読者の側から見える振る舞いは「JSX に書くだけで済む」という点に尽きます。
二重登録や、DOM から外した要素にハンドラが残る問題は、そもそも開発者が登録を書かないので起きません。
(4) 状態更新後の再描画トリガ → useState とレンダースケジューリング
Vanilla JS では「状態を変えたら最後に render()」というルールを人間が守りました。React では、状態を更新する API を呼ぶことが、そのままレンダーの依頼になる 設計です。
// Part 3 で登場
const [todos, setTodos] = useState([]);
function addTodo(text) {
setTodos([...todos, { id: nextId++, text, completed: false }]);
// この呼び出しが自動で再レンダーをスケジュールする。自分で render を呼ばない
}詳細は 3.4 状態 (state) — Reactに再レンダーを依頼する唯一の方法 にあります。再レンダーのタイミング制御(バッチ処理、優先度)までは内部実装なので、6.2 Reconciliation と Fiber でまとめます。
(5) 安全なテキスト挿入 → JSX のエスケープ
React は JSX の {expression} に入った文字列を、デフォルトで HTML エスケープします。
// 安全: どんな文字列が入っても HTML として解釈されない
<span>{todo.text}</span><img src=x onerror=...> という形の文字列が todo.text に入っても、画面にはその文字列が そのまま表示されるだけ です。スクリプトとして実行はされません。Vanilla JS で innerHTML に頼ろうとした瞬間に背負う XSS リスクを、React では既定で回避できます。
(ただし React にも、意図的に危険な挿入をする dangerouslySetInnerHTML という API があります。名前が長いのは「使うときは一度立ち止まる」という合図だと捉えてください。dangerouslySetInnerHTML の詳細 は公式リファレンスに譲ります。)
2.5.3 対応表
図 2.5.1 Vanilla JS で手書きした仕事と React の対応
上の図を、もう少し「見て腑に落ちる」形に並べ直したのが次の図です。左が Part 2 で手で書いた仕事、右が Part 3 以降で React が引き受ける仕事。線が 1 本ずつ対応しています。
図 2.5.2 手作業と自動化の対応(視覚版)
この図が、Part 2 の全体を 1 枚にまとめた見取り図です。Part 3 以降を読んでいて「あれ、これ何のためにあるんだっけ」と迷ったら、この図に戻ってきてもらえればと思います。
2.5.4 宣言的 UI という言い方
ここまでの 5 点をひとことでまとめると「宣言的 UI」になります。
- 命令的 (imperative) — 「どう動かすか」を 1 手ずつ書く。
createElement→appendChild→addEventListener→ ... - 宣言的 (declarative) — 「今こう見えるべきだ」を書く。その状態に DOM を合わせるのは、フレームワーク側の仕事。
Vanilla JS で UI を書き始めると、最初はコード量が少なく見えます。でも機能が増えるにつれて 状態と DOM の同期ロジック が膨らみ、ほとんどのバグがそこから出てきます。React はその同期を自動化することで、開発者の注意を「状態の設計」と「状態からの UI 記述」に集中させる、という発想のフレームワークです。
2.5.5 Vanilla JS 版の完成形を手元で走らせる
比較の基準として、Part 2 で作った Vanilla JS 版の完成コード(2.3 の差分更新版)を手元で動かしておくことをおすすめします。手順は単純です。
- 任意のフォルダに
index.htmlを作る - 2.2 Vanilla JS 実装 と 2.3 差分更新を手で書く のコードを貼り込む(HTML 本体と、末尾に
<script>タグで JavaScript) - ブラウザでそのファイルを直接開く(ダブルクリックで十分)
ビルドツールやサーバーは不要です。index.html 1 枚で完結します。以降の章で React 側のコードと並べて比較する際、この Vanilla JS 版を開き直せるようにしておくと便利です。
2.5.6 次の Part へ
Part 3 では環境セットアップから始めて、React の最小構成を書きます。ここまでで感じた「面倒」の 1 つずつが消えていく様子を確かめてください。まずは 3.1 開発環境 — Vite + React + TypeScript から進みましょう。