はじめに
React(通称 React.js1)を全く知らない、あるいは幾つか記事を見たけどなんなのかピンと来ていない、という人のために書いています。
「jQuery くらいしか知らない」くらいの人に具体的に雰囲気を知ってもらうのが目的であり、すでにやる気がある人向けのチュートリアルではありません。やる気が出れば日本語版ドキュメントを読んで手を動かせばあっという間なので、そこまでの興味が出ることを目標にしています。
以降では ES2015 (ES6) の文法(アロー関数とか)を使っています。この部分が怪しい人は先にアロー関数と const
文だけでも知ってから先に進んでください。
以下の説明中、このアイコンで表すのは(2023 年現在から見た)『昔話』です。新しく自分のコードを書く際には本来知らなくていいことですが、古い記事を見たときに混同しないための参考情報として書いてあります。この記事自体は React の最新に追従するように定期的に更新し、表現を見直しています。
React がやること
React がやることは非常にシンプルで、API も数えるほどしかありません。「暗記力を使って覚えることの少なさ」に関してはピカイチです。そのわずかな API に、みんな慣れ親しんできた jQuery のメソッドの大部分を駆逐してしまうほどの威力があります。
React がやることを 3 行で説明すると、こうなります。
- ページ状態を保持している「プレーンな JavaScript のオブジェクト」に、
- 「テンプレート的な関数」を作用させて、「仮想 DOM」と呼ばれるDOM の設計図を取り出し、
- その設計図を使って本物の DOM を構築する。
ここでいう「プレーンな JavaScript のオブジェクト」とは、JSON をパースしたりするだけで取得でき、タグ文字列などを一切含まない、クリーンなデータを保持しているオブジェクトと思ってください。
Web API などからプレーンなオブジェクトを取り出し、そこから DOM を作り出すことは jQuery でもよくやる作業です。例えば「商品データを一覧表示」なら、愚直にやるとこんな感じ。
$.getJSON("/api/items").then((data) => {
const ul = $("ul.item-list").empty();
data.items.forEach((item) => {
const li = $("<li>").addClass("item").appendTo(ul);
if (item.stock === 0) li.addClass("soldout");
$("<div>").addClass("item-name").text(item.name).appendTo(li);
$("<div>").addClass("item-price").text(item.price).appendTo(li);
});
});
難しいことはしていませんが、見た目に直感的ではない感じはします。
同じことを React で書くと、こういう全く違う見た目になります(行数が増えているのは関数を分割したりしているから)。
// ItemListのコンポーネント定義(実体は関数)
const ItemList = (props) => {
return (
<ul className="item-list">
{props.items.map((item) => (
<ItemDetail item={item} />
))}
</ul>
);
};
// ItemDetailのコンポーネント定義(実体は関数)
const ItemDetail = (props) => {
const item = props.item;
return (
<li className={"item" + item.stock === 0 ? " soldout" : ""}>
<div className="item-name">{item.name}</div>
<div className="item-price">{item.price}</div>
</li>
);
};
fetch("/api/items")
.then((res) => res.json())
.then((data) => {
ReactDOM.createRoot().render(
<ItemList items={data.items} />, // これを
document.getElementById("container") // ここにレンダーしろ
);
});
え~ととりあえず、面妖な外見の HTML タグっぽいものを見て速攻逃げ帰りたくなった人がいますよね。なんと React では、このように JavaScript と HTML(?) が悪魔合体しているのが基本作法です。まあ少し我慢してお付き合いください。筆者も当初はこの壮絶な外見を嫌悪して逃げていたクチですが、1 日経たずにあっさり寝返りました。後でもう少し詳しく述べますが、とりあえずこの**「タグ」は実際には単なる関数呼び出し**であり、Babel などに通すと機械的に関数呼び出しに変換されるので、これはあくまで JavaScript です。$.getJSON()
の代わりに使っている fetch
はこれですが、お好みで superagent や axios を使っても構いません。
DOM を直接切ったり貼ったりする部分はなくなり、代わりに 2 つの関数(コンポーネント)を定義しています。それぞれの関数はなにやら「HTML タグっぽいもの」を返しており、その中で JavaScript の式(変数)が {}
で埋め込まれています。この「タグっぽいもの」が、巷で話題の仮想 DOM と呼ばれているアレです。
React の API を露骨に呼び出しているのは最後の ReactDOM.createRoot().render()
だけ。しかもこれがこの記事に出てくる唯一の React の API です。データを表示するだけならとりあえずこれさえあれば作れます。
古い記事で
class
構文(2019 年頃まで)を使ったりReact.createClass
という API(2015 年頃まで)を使ってコンポーネントを定義している記事を見た人がいるかもしれませんが、これらは既にレガシーとなっています。現在ではコンポーネントは基本的にこのように単なる関数で記述します。
ぱっと見、サーバサイド言語でもよく使われている、いわゆる「テンプレート」に似ている気がしませんか。ただしテンプレートがあくまで文字列ベースの処理でありその後に HTML の字句解析処理を要するのに対し、React は**仮想 DOM と呼ばれる2「DOM の設計図」**をベースに、実 DOM を直に処理します。 ItemList
や ItemDetail
といった関数が返している「タグっぽいもの」は本物の DOM 要素ではなく、あくまで設計図であり、つまりは非常に軽量な JavaScript のオブジェクトです。
古典的なテンプレートエンジンでは、開発者はそのエンジン独自のテンプレート構文(ループとか)を覚え、テンプレートファイルを書いていきます。その代わりに React では、開発者は DOM の設計図を return する関数を JavaScript で書いていきます。この HTML タグっぽい記法と {}
による任意の式の埋め込み以外に、独自規則はありません。ループや条件分岐は、見ての通り普段 JavaScript で使っている三項演算子や Array.prototype.map
をそのまま使うだけです。
タグっぽい記述(の関数呼び出し)を通して仮想 DOM を作ったら、ReactDOM.render
が、それに対応する本物の DOM 要素を指定した場所に構築してくれます。これが、開発者が手でいちいち appendTo()
とか removeClass()
とか text()
とか val()
とかの jQuery 的な DOM 操作を書かなくて済む理由です。
React とは基本これだけをするライブラリです。んー、大したことない気がしませんか。というか、これってサーバサイド言語(PHP とか JSP とか)で使ってきたテンプレートを、ちょっと面倒くさくしただけではないでしょうか。テンプレートと比べて何が嬉しいのでしょう。なぜこれだけのライブラリが、一部界隈で、まるで世界を革命する力であるかのように言われているのでしょう? 以下で述べていきます。
理由: JSX が便利で安全
既に見たとおり、React では JSX と呼ばれる HTML チックな記法を JavaScript に導入しています。これが必要な理由は単純で、普通の JavaScript の構文だけを使って DOM の設計図を読み書きするのは人間には辛いからです。JSON 的なデータを記述するには JavaScript の構文は最強ですが、「要素には属性があって、子にはテキストノードや別の要素があって」という HTML/XML 的なデータ構造を、素の JavaScript は効率的に表現できません。
JSX/HTML/XML の記法で <div title="message">Hello <b>World</b></div>
で直感的に表現できる構造は、もし JSON で書くと { element: 'div', attributes: { title: 'message' }, children: [ 'Hello ', { element: 'b', children: ['World'] } ] }
のようになります。イヤですね。
上記の「タグ」は Babel や TypeScript や CoffeeScript (>=2.0) を通すと、機械的に以下のようになります。
React.createElement(
"div",
{ title: "message" },
"Hello ",
React.createElement("b", null, "World")
);
試したい方は Babel の REPL にいろいろ入れてみましょう(react
プリセットは ON にしてください)。要するに <Element a="b" c={d}>Text</Element>
は React.createElement(Element, {a: 'b', c: d}, 'Text')
のような JavaScript コードに変換されます3。この関数は実行されるとまさに上記の JSON に似たオブジェクトを出力し、それが仮想 DOM の最終的な正体です。これだけなので、これを手書きしたい人は JSX を使わなくてもいいですし、実際悟りを開き切った React 開発者の中には JSX が要らなくなる人もいるようです。本記事では JSX 使うべきということで通します4。
JSX 記法さえあれば、古典的なテンプレートエンジンが頑張って実装してきたループ・条件分岐・子テンプレート呼び出し(関数呼び出しのこと)・数値計算といった機能は、JavaScript には全部元から備わっており、しかも超高速なのですから、使わない手はありません。
Babel や esbuild や TypeScript や CoffeeScript は普通に JSX をサポートしており、これらに馴染みがあれば導入のハードルは低いです。JSX 自体は汎用性の高い単なるシンタックスシュガーなので、他の多くのライブラリでも採用されており、各種エディタでのサポートも非常に良好です。もはや事実上の標準といっても過言ではない状態になっています。
2015 年ごろまでは JSX 構文周りのエコシステムが貧弱で、React の開発チームが専用の JSX 変換ツールを配布したりしていました。その時の名残で「React は altJS と相性が悪い」という記事がたまに残っています。
慣れ親しんだテンプレートと比べるとループの可読性はわずかに落ちますが、タグの対応ミスなどのシンプルな構文エラーはコンパイル時にチェックされ、エスケープ漏れなどで XSS 脆弱性が入る余地もほぼありません。TypeScript を使えば厳密な型チェックも入ります。
JSX は最初は違和感が強いでしょうが、実はこれは、XML が今よりずっとメジャーだった時代に提案され、Firefox に実装もされていた E4X 記法とそっくりです。まだ JSON がなかった昔、今の JSON の位置に XML を使う未来が想像されていた時代があり、その時代には文字列リテラルを
""
で囲むのと同じ感覚で、XML リテラルを<hoge></hoge>
で囲んで JavaScript で書くことは自然だと思われていたのです。それを知っていれば、JSX は JavaScript のごく自然な拡張に見えてくるかも。
そして、このように HTML と JavaScript が悪魔合体していることは、禁忌どころか、大事なメリットなのだと考えましょう。「HTML と JavaScript は分離せよ」は長い間の鋼の掟でしたが、それは、HTML こそが単独でも成立する主役ドキュメントであり、サーバサイドで HTML 内に実データを頑張って埋め込んでおり、JavaScript がおまけだった時代の話です。SPA で殆どの実データが API 経由でやってきて、あらゆるものが動的に構築されるようになると、HTML 部分は「中身のない <ul>
」「カレンダーやスライダに変身させられるのを待っているだけの <div>
」「<form action=...>
と結びつかないフォーム要素」「見出し行だけのテーブル」「クリックしてもどこにも飛ばない <a>
や <button>
」等々、JavaScript なしには意味を持たない**“抜け殻”**が静的に陳列されるだけの場と化していきます。CSS の表現力向上によって HTML タグのレベルでデザインをごにょごにょする必要性は下がり続けています。ある一線を越えたらもう、抜け殻の HTML タグだけを別ファイルで独立してメンテし、遠距離狙撃で .datepicker()
や .val()
を撃ちまくることは、重要でも効率的でもないのです。むしろ機能的に関連するタグと動作を、名前付きでまとめて短く記述できる React の記述方法こそが楽で合理的になります。styled-components などの CSS-in-JS の技法を使えば CSS も 1 ファイルにまとめ、自己完結したコンポーネントが作れます。
「JSX だと実装とデザインが分離できずデザイナーが辛いのでは」という点が気になる人がいるでしょうが、React でも規模に応じ、ビュー特化の(純粋にテンプレート的な)コンポーネントとロジックが主体のコンポーネントを分けられます。詳細はこれとかこれを参照してください。まあ分けないほうが楽なことも多いので規模やポリシー次第です。
「HTML と JavaScript(と CSS)を 1 つにまとめてコンポーネントという単位で管理すべき」という思想自体は、React 対抗としてよく名前が挙がる Vue や Svelte でも共通です。
セマンティックな HTML を失うことによるアクセシビリティ低下が気になる人は ARIA を学びましょう。
また、JSX は JavaScript のシンタックスシュガーなので、「真偽値」「数値」「関数」「配列」「オブジェクト」といった、HTML 言語では元来直接扱うのが難しかった値を、JSX では何の苦労もなく扱えます。
理由: テンプレートより圧倒的に速い差分描画
何らかの理由(ユーザ操作など)でページの状態が変わり画面を書き直す場合、React はゼロから DOM を毎回作り直すのではありません。画面を更新する際、React は新旧の設計図を見比べて差分を計算し、パッチ的に DOM 操作を適用します。
開発者がやることは、新しい状態が含まれた新しいプレーンな JavaScript オブジェクトを準備して、さっきの ReactDOM.render()
で再描画するだけです。あとは React が以前の設計図と見比べ、勝手に jQuery でいうところの remove()
や val()
や prop()
や addClass()
などなどに対応する操作に置き換えて実行します。これらを人間が手で書かなくても大丈夫。
この差分描画はとても高速なため、ウェブページ全体を React 化して適用してしまうことが普通に可能です。1 キーストロークごとに、アプリケーション丸ごとの状態が含まれたプレーンな JavaScript オブジェクトでアプリケーション全体を「再描画」しても、割と快適に動作します(とても複雑なページだと最適化が必要)。これは文字列ベースのテンプレートでは遅すぎて到底できない芸当です。仮想 DOM の比較は JavaScript 内のみで完結する軽い演算なので、HTML 文字列パースを伴う操作に比べると圧倒的に速いのです。
React を使えば、「状態を反映するよう DOM をどう変更すべきか」を忘れてしまい、「ある状態に対応する DOM はどうあるべきか」の定義に集中できます。別の言い方をすると、「ロード中はこれら 5 個のボタンを一時的に無効化してここにインジケータを表示して…」「こちらの選択内容でこちらのフォームの形式が変わって…」みたいな面倒な状況で勝手にページ全体の一貫性が保たれます。
この威力は絶大です。さっきの jQuery の例は、あれだけだったら別に大したことなかったですが、ユーザ操作に応じて在庫有無やユーザ評価やカラバリ選択ボックスやポップアップメニューを動的に付与したり、同じ商品やボタンを 2 カ所に表示しつつ同期したり、商品をフィルタリングしたりソートしたり…といった様々な動きをつけていくと混沌の扉が開きはじめます。サンプルレベルでは React の威力は実感しづらいのですが、実用的なアプリではバグの心配が激減します。
更に、ページ全体の情報を 1 箇所に集められるので、「ブラウザをリロードされても状態を復元する」「ユーザ操作を元に戻す/やり直す」といったことの実装も比較的容易です。
理由: 超軽量なコンポーネント
React では、「部品」「独自タグ」的なものがアホみたいに簡単に書けます。それを組み合わせるのも非常に簡単です。コンポーネント定義といっても何しろ単なる関数であり、ワンライナーでも書けます。コンポーネント利用の際も、標準の HTML 要素と独自タグの境界はほとんどなく、まるで HTML に突然フル機能のカレンダー「タグ」や掲示板「タグ」が実装されたかのような感覚で扱えます。要素名が小文字で始まるとネイティブのタグ(span
, ul
など)、大文字で始まるとユーザ定義のコンポーネントです。
// 1行でコンポーネントを定義
const Heading = (props) => <h1>{props.title} <em>{props.subtitle}</em></h1>;
// コンポーネントの使用
const virtualDOM = <Heading title="Hello" subtitle="React" />;
これは極端にシンプルな例ではありますが、実際のアプリケーションを作る際も、コンポーネントはできるだけ単純な関数で書き、それを組み合わせてアプリにしていきます。数行~十数行で済む「ヘッダーコンポーネント」「目次コンポーネント」とかを何十個も書いて、最終的に 1 個のアプリを作り上げていくイメージ。この手軽さは、ちょっと jQuery UI とかのコンポーネントには真似できません。Subversion のブランチが Git のブランチに変わったくらいのインパクトがあります。
ただしこんな芸当が可能であるために、「コンポーネントが原則として内部状態を持たない(ステートレス)」、ということがとても重要です。というわけで次の話。
理由: 原則的にステートレスなコンポーネント
コンポーネントがステートレスである、とは、返す DOM の設計図が、外部から「タグの属性」(props
)として与えられた情報のみで一意に決まるということです(数学的な意味での関数に近い)5。この思想のお陰で、コンポーネントは単なる関数で書くことができます。
React を使うにあたって、jQuery から一番考え方を変えないといけないのがこの部分でしょう。jQuery のコンポーネント(カレンダーでも、スライダーでも)は内部状態(プロパティ/ステート)として「現在の入力値」とか「disabled かどうか」とかを持っているのが当たり前でしたし、それの初期化と出し入れで記述が長くなる傾向にありました。React のコンポーネントでは、大事な状態ほど外部から流し込まれます。コンポーネントとは外部からの情報を DOM 設計図に変換するプリズムに過ぎません。そう心得て、可能な限りそのように作るべきです。
…と書きましたが、 関数コンポーネント内でフックを使うことで、内部状態を持ったコンポーネントも書けます。
ただしステートにするのは「親コンポーネントと一切やりとりする必要がないプライベートな内部状態」だけです。「ドロップダウンメニューが今ドロップダウンされている最中かどうか」とか。
「外部に状態がある」というのはカスタムコンポーネントだけの話ではありません。React では <input>
などの標準フォーム要素ですらその発想が徹底しており、ステートレスな振る舞いをするようになっています。以下の例では value
が「外から」固定値で与えられているので、この input
要素はリードオンリーになり、中身を変更できません。
See the Pen React examples on CodePen.
編集可能にしたければどうするのかというと、例の「ページ状態を管理しているプレーンな JavaScript オブジェクト」を経由します。テキストボックス内で入力操作が起きた場合、内部状態が変わってからイベントが発火して外部状態を更新するのではなく、コールバック関数経由で外部状態を先に変更してから画面を「再描画」するのです。こんなイメージ。
See the Pen paVbjx by naruto (@narutan) on CodePen.
回りくどいと思うでしょうが、綺麗な元データ ("single source of truth") が外部の 1 カ所だけにあり、DOM はそれを反映しているだけ、というこの構造は、複雑なフォーム・アプリほど楽さが際立ちます。jQuery で設定画面などのフォームを書くと、val()
や text()
を駆使して、「生データの状態を DOM に反映させる」のと、逆の「DOM の状態から生データを再構築する」のを両方を書くことになりますが、この構造ならそういう作業は要りません。(One-way data flow という標語を聞いたことがある人は、それが指しているのはこれのことです。)
上記の例で、太古の黒歴史とされてきた
onChange
やonClick
属性が華麗に復活しているのを見て驚いた人がいるかもしれませんが、React の設計思想的には何も問題ありませんので、こういう新しい記法だと思いましょう。内部ではちゃんといろいろ最適化されています。
例えばこのくらいの複雑さのフォームは、React だと jQuery UI の半分の行数と 1/5 の時間で実装できる感じ(6 個のステートレスコンポーネントの組み合わせ。ステート付きコンポーネントは自力では書いていませんが、React-Bootstrap を使用しています。他にも便利な UI ライブラリがたくさんあります)。
さて、React 自体は単機能の描画ライブラリであり、「外部の状態」をどこにどうやってどんな粒度で保存すべきかについて一切関知しません。小さなページやウィジェットなら、上記サンプルのように状態をオブジェクトに放り込んで全体をまるっと更新するので何の問題もありません(実際には render
を何度も呼ぶのではなく最上位コンポーネントの state としてそのようなオブジェクトを保持するほうが普通です)。しかし大規模プロジェクトで上記のように「大事な状態ほど外部に置く」を突き詰めていくと、トップに巨大な状態オブジェクトが産まれ、「グローバル変数だらけ」と類似のカオスに陥る危険があります。これを防ぐため、「外部状態」を秩序立てて管理するための別ライブラリを混ぜて使う需要があり、代表的なものが Redux や Recoil といったものです(厳密には React 外の話ですが)。必要に応じて使いましょう。
よく誤解されていますが、Redux のようなものは必須ではありません。React のドキュメントなどでも一切使われていません。小さなプロダクトで Redux のような専用の状態管理ツールをいきなり導入してもタイプ量が増えるばかりで実用上のメリットがよく分からないと感じると思います。既にユニットテストなどの概念が骨身に染みついた上で大規模開発をする人以外は、まず Redux なしで React の考え方に慣れましょう。また、公式の Redux Toolkit を使うとボイラープレートがかなり減らせて TypeScript との相性も良いので、新規で Redux を使う場合はお勧めします。
React が向かない場合
もちろん React が常に万能なわけではありません(タイトルが釣りっぽいのはごめんなさい)。React の弱点、React が向かない場合もありますのでよく考えて使いましょう。
- React 自体の理解に加えて、Babel/TypeScript のようなトランスパイラの知識は(絶対ではないけど)ほぼ必須で、Webpack のようなモジュールバンドラの知識も(絶対ではないけど)ほぼ必須。そこまで至っていない人にとっては多少学習コストはあります。React 公式のスターターやフレームワークを使って 3 分で React アプリの雛形を作ることもできますが、まあ内部で何をやっているのかは知っておいた方が良いでしょう。
- 複雑で動的なものほど React は威力を発揮しますが、HTML が主役で動的な要素が少ないサイトや 1 日で書き捨てるようなサイトは jQuery でも特に困りません(jQuery の方が楽な訳でもないですが)。大雑把ですが、作ろうとしているものが「アプリ(HTML が 1 で JavaScript が 9)」なら断然 React が良いはずですが、「記事(HTML が 9 で JavaScript が 1)」なのであれば学習コストの兼ね合いでの判断になります。
- あくまで JavaScript ベースのライブラリなので、デザイナ系の人の理解と協力が必要(とはいえそんなに難しくないはずであることは前述しました)。
- 大抵の場合はサーバ側の協力も必要(基本的にはサーバ側は楽になります)。純な React アプリでは HTML は数行くらいの固定ファイルになり、あらゆる実データは REST や GraphQL などの API 経由で来ます。サーバサイドに必要なのは API であって、テンプレートで HTML にデータを埋め込むことではありません。
- React 最新版では IE8 以下は切り捨て。も、もういいよね…
- React にとって DOM とはあくまで外部状態を投影するためのスクリーンなので、逆に DOM からデータを取ってくるような作業(スクレイピング)は行えません。
結論: You Don't Need jQuery?
以前You Don't Need jQueryという記事が話題になっていましたが、はてブとかの反応を見る限り、「むしろ jQuery の使いやすさが良く分かった」という人が予想外に多い感じでした。
でもその記事の冒頭部分には「DOM を直接操作することはアンチパターンとなりました」とあり、記事はその前提で書かれています。「(頑張れば標準 API でも jQuery と同じことを書けるから)もう jQuery は必要ない」ではなく、もっと根本的に「(jQuery 的な DOM 操作なんか人間がやることじゃないから)もう jQuery は必要ない」です。jQuery を使うことで標準 API より数文字節約できるような作業の大部分は、そもそも 1 行も書かなくてよくなったのです。
$.proxy
とか $.each
とか $.ajax
とか $.extend
とかの「昔は便利だった」jQuery の(DOM 操作以外の)ユーティリティメソッドも今はすべて標準で代替可能であり、2023 年現在ではこのメリットも一切ありません。そう気づいてしまい、残った jQuery 依存を消し去りたいという衝動に駆られたら、はじめて、ああいう記事の出番なわけです。わずか数カ所の数文字のタイプ数増加で、100KB 超のライブラリを捨て去れるなら、Web でのメリットは大きいです。
元記事にもリンクされていますが、個人的にはYou Might Not Need jQueryというサイトがまとまっていてお勧め。
註: お陰様でよく読まれる記事になっていますので、定期的に加筆修正やアップデートを行っています。最後の更新は 2023 年 6 月です。いま見当違いのように見えるブクマコメント等は、大抵は私が加筆したせいです。
-
一応 "React.js" だの "ReactJS" だのというのは俗称です。ちなみに Angular もバージョン 2 からは後ろに JS が付かないのが正式名称です。 ↩
-
「仮想 DOM」は、ドキュメントなどでは「React 要素」と呼ばれています。これは React の応用先が今やブラウザ上の DOM のみに限られないからです。この記事では有名かつ直感的な名称である「仮想 DOM」を使用します。 ↩
-
この
React.createElement
の部分はトランスパイラの設定などで変更できるので、React 以外のライブラリも JSX を利用できます。 ↩ -
例外として、バンドラもトランスパイラも使わずほんのちょっぴり React を使いたいだけの場合は JSX なしという選択肢もあり得ます。公式サイトのこちらやこちらを参照。 ↩
-
厳密には「ピュアレンダ」と「ステートレス」とは別の概念として区別する必要がありますが、この記事では割愛します。 ↩