Skip to content

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.3render 関数の中で todos 配列を走査し、li を作る
2. 差分計算2.3既存 li と新しい todos を id で突き合わせて挿入・更新・削除
3. イベントハンドラの寿命管理2.3 / 2.4作成時に 1 度だけ登録、更新時は登録しない
4. 状態更新後の再描画トリガ2.2 / 2.4状態を変えた各所で render() を呼ぶ
5. 安全なテキスト挿入2.4innerHTML ではなく textContent / createElement を使う

これらは 1 行コードではなく、ルールや習慣の集合です。ルールを破ると同期バグに直結します。ここがしんどいところですね。

2.5.2 それぞれが React のどの機能に置き換わるか ​

上の 5 つが React では何に対応するのかを見ていきます。各項目は Part 3 以降で詳しく扱うので、ここではリンクと 1〜2 文の要約に留めます。

(1) 状態 → DOM のマッピング → JSX ​

render 関数の中で createElement("li") を延々と呼んでいた箇所は、React では JSX で書きます。

tsx
// 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 を呼ぶことが、そのままレンダーの依頼になる 設計です。

tsx
// 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 エスケープします。

tsx
// 安全: どんな文字列が入っても 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 手作業と自動化の対応(視覚版) ​

Vanilla JS で手書きReact が自動で担当1. 状態 → DOM のマッピングcreateElement + appendChild を延々とJSX「こう見えるべき」を書くだけ2. 差分計算dataset.id で突合 / 60 行手書きVirtual DOM + Reconciliationkey プロパティで要素を識別3. ハンドラの寿命管理add / remove の対を自分で揃えるJSX の onXxx(自動登録)合成イベントでルートに集約4. 再描画のトリガ各所で render() を呼ぶ(忘れがち)useState / setState呼ぶこと自体が再レンダー依頼5. 安全なテキスト挿入innerHTML 禁止のルール徹底JSX の {} は自動エスケープ意図的な危険挿入だけ別名 API

この図が、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 の差分更新版)を手元で動かしておくことをおすすめします。手順は単純です。

  1. 任意のフォルダに index.html を作る
  2. 2.2 Vanilla JS 実装 と 2.3 差分更新を手で書く のコードを貼り込む(HTML 本体と、末尾に <script> タグで JavaScript)
  3. ブラウザでそのファイルを直接開く(ダブルクリックで十分)

ビルドツールやサーバーは不要です。index.html 1 枚で完結します。以降の章で React 側のコードと並べて比較する際、この Vanilla JS 版を開き直せるようにしておくと便利です。

2.5.6 次の Part へ ​

Part 3 では環境セットアップから始めて、React の最小構成を書きます。ここまでで感じた「面倒」の 1 つずつが消えていく様子を確かめてください。まずは 3.1 開発環境 — Vite + React + TypeScript から進みましょう。

参照ソース ​