Skip to content

1.2 DOM を直接触る世界 — jQuery 時代の開発 ​

ここでは、React が登場する前の Web フロントエンドで広く使われていた jQuery の世界を覗いてみましょう。昔話のように見えるかもしれませんが、ここで体験される「DOM を直接触る感覚」は、React が解こうとした問題を理解する上で大事な土台になります。

まず、この章で伝えたいことを 1 行で言います。

jQuery の時代は「イベントが起きたら DOM を直接書き換える」命令的なスタイルが当たり前で、それが後の苦しみの種にもなりました。

2006 年から 2010 年代後半にかけて、Web フロントエンドの実装は jQuery という 1 本のライブラリに大きく寄りかかっていました。jQuery は 2006 年 8 月 26 日に John Resig が公開したライブラリで、その原型は同年 1 月の BarCamp NYC で構想されたとされています。ピーク時 (2019 年) には上位 100 万サイトの約 80% で使われていたという記録があり、当時の Web 開発者にとって半ば共通言語のような存在でした。

この章では、当時なぜ jQuery がそれほど普及したのか、そして jQuery の提供する「DOM を直接触る」スタイルがどのような記述を生み出していたのかを、具体的なコードで確認します。次章 1.3 命令的 UI の限界 で、その先にあった苦しみを掘り下げます。

jQuery が解いた問題 ​

jQuery が登場した 2006 年当時、ブラウザ間の互換性は深刻な問題でした。Internet Explorer 6 / 7 と、Firefox、Safari、Opera では、DOM API の挙動やイベント処理、Ajax 通信の書き方がそれぞれ違い、同じコードが動かないことが日常でした。想像してみてください。せっかく書いたコードが「Firefox では動くけど IE では動かない」となり、if 分岐で両対応を書くような世界です。

jQuery は次の 4 領域をラップして、ブラウザ差を吸収する統一 API を提供しました。

  • DOM トラバーサル・操作 — .find()、.closest()、.append()、.addClass() など
  • イベント処理 — .on('click', ...)、.trigger(...) など
  • Ajax 通信 — $.ajax()、$.get()、$.post()
  • エフェクト / アニメーション — .fadeIn()、.slideUp()、.animate()

本サイトの読者は HTML / CSS を学習中のはずなので、生の DOM API で同じことを書くとどうなるかを、まず 1 つ確認しておきます。

生の DOM API での書き方 ​

MDN によれば Document.querySelector() は指定された CSS セレクタに一致するドキュメント内の最初の Element を返します。2015 年以降に広くブラウザで利用可能になった API で、現在では第一選択です。

html
<!doctype html>
<button id="say-hi">クリック</button>
<p id="msg"></p>
<script>
  const btn = document.querySelector("#say-hi");
  const msg = document.querySelector("#msg");

  btn.addEventListener("click", () => {
    msg.textContent = "こんにちは";
    msg.classList.add("highlighted");
  });
</script>

addEventListener(type, listener) でイベントを登録し、textContent で中身のテキストを差し替え、classList.add() でクラスを足しています。

同じことを jQuery で書く ​

同じ画面を jQuery で書くと、こうなります。

html
<!doctype html>
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
<button id="say-hi">クリック</button>
<p id="msg"></p>
<script>
  $("#say-hi").on("click", function () {
    $("#msg").text("こんにちは").addClass("highlighted");
  });
</script>

$("#say-hi") でセレクタに一致する要素の集合を包み、.on("click", ...) でイベントを登録、.text(...) で中身を差し替え、.addClass(...) でクラスを追加しています。メソッドチェーン (返り値が同じオブジェクトなので .text(...).addClass(...) と連結できる) が特徴的でした。

生の DOM API と比較して、jQuery の旨味は次の 3 点に集約されます。

  1. セレクタ 1 つで 複数要素 を同じ書式で操作できる ($("li.item").addClass("active") のように)
  2. メソッドチェーンで短く書ける
  3. ブラウザ差を jQuery 側が吸収してくれる

図 1.2.1 イベントが来てから DOM が変わるまで (jQuery) ​

ユーザークリックeventイベントハンドラ$(...).on('click', ...)中で DOM を直接操作手で書き換えDOM.text(...) / .addClass(...)その場で上書き「状態」を抽出する層はなく、画面そのものが状態

jQuery 時代の典型的な構造 ​

jQuery で小〜中規模の SPA を書くとき、コードは概ね次のような形を取ります。

図 1.2.2 jQuery スタイルのイベント駆動構造 ​

イベントごとにハンドラを書き、ハンドラの中で 今の DOM を直接読んで / 書いて 画面を更新します。「状態」という概念を抽出した層はなく、状態は DOM それ自体 あるいは JavaScript の局所変数に散らばる構造です。

具体例として、ToDo リストの追加処理を jQuery で書くとこうなります。

js
// ToDo 追加
$("#add").on("click", function () {
  const text = $("#input").val();
  if (!text) return;

  $("<li>")
    .addClass("todo")
    .text(text)
    .appendTo("#list");

  $("#input").val("");
});

// 完了マーク
$("#list").on("click", "li.todo", function () {
  $(this).toggleClass("done");
});

新しい <li> を作って #list に追加し、クリックされたら .done クラスをトグルする、という素直なコードです。画面に出ているもの = DOM そのもの で、JavaScript 側にアプリの状態を別途持つ必要はありません。小さなアプリではこれで十分に動きます。

副作用: 命令的スタイルが前提になる ​

jQuery が定着した結果、Web フロントエンドの実装は 命令的 (imperative) なスタイルが主流になりました。「状態がこうなら画面はこうあるべき」という宣言ではなく、「イベントが起きたら、DOM をこう書き換えろ」という手順の列として UI を記述する書き方です。

  • 「ユーザーがログインしたら、ヘッダーのボタンを隠して、サイドバーを出して、<main> に通知バッジを足す」
  • 「タブが切り替わったら、前のタブを inactive にして、次のタブを active にして、中身を差し替える」
  • 「バリデーションに失敗したら、フォームの下にエラーメッセージを追加して、入力欄を赤くする」

いずれも「何をどの順序で DOM に適用するか」を開発者が 1 行ずつ書きます。書くべき遷移が n 個あり、状態の組み合わせが m 個あると、考慮すべき遷移は最悪 n × m 個に膨らみます。小さなアプリではわかりやすく、大きなアプリでは途端に手に負えなくなる のがこのスタイルの特徴です。少し肌寒い予感がする、と思っていただければ正解です。

この「手に負えなさ」が具体的にどう噴き出したか、そして React がそれにどう答えたかを、次章で見ていきます。

1.3 命令的 UI の限界 — 大規模 SPA で起きた問題 に進んでください。

参照ソース ​