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 で、現在では第一選択です。
<!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 で書くと、こうなります。
<!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 つで 複数要素 を同じ書式で操作できる (
$("li.item").addClass("active")のように) - メソッドチェーンで短く書ける
- ブラウザ差を jQuery 側が吸収してくれる
図 1.2.1 イベントが来てから DOM が変わるまで (jQuery)
jQuery 時代の典型的な構造
jQuery で小〜中規模の SPA を書くとき、コードは概ね次のような形を取ります。
図 1.2.2 jQuery スタイルのイベント駆動構造
イベントごとにハンドラを書き、ハンドラの中で 今の DOM を直接読んで / 書いて 画面を更新します。「状態」という概念を抽出した層はなく、状態は DOM それ自体 あるいは JavaScript の局所変数に散らばる構造です。
具体例として、ToDo リストの追加処理を jQuery で書くとこうなります。
// 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 で起きた問題 に進んでください。
参照ソース
- jQuery API Documentation (api.jquery.com)
- MDN — Document.querySelector()
- MDN — EventTarget.addEventListener()
- Wikipedia — jQuery(John Resig による 2006-08-26 公開、ピーク時シェア等の参考)