Jordan WalkeがReactで導入した再利用可能なコンポーネント、宣言的ビュー、状態駆動レンダリングが、現代のフロントエンドアーキテクチャをどう変えたかを解説します。

Jordan Walkeは、Facebookで働いているときにReactを作ったことで知られるソフトウェアエンジニアです。React以前のフロントエンドは、ページやテンプレートを中心に構築され、HTML・CSS・JavaScriptを同期させるための“グルーコード”が増えていくのが普通でした。Walkeの重要な発想はモデルをひっくり返すことでした。つまり、UIを時間をかけてパッチするドキュメントの集合として扱うのではなく、小さく再利用可能なコンポーネントの木として扱い、それらを組み合わせて大きな機能を作る、という考えです。
これは単なる新しいライブラリではなく、UIの設計思想そのものの変化でした。コンポーネントは、インターフェースの一部とそれに必要なロジックや状態を一緒にまとめ、外部にはきれいなインターフェース(props)を提供します。これにより、UIは1つの壊れやすいページを編集するよりも、レゴの積み木で作る感覚に近くなります。
Reactが重要だった理由は、チームが次のように動けるようにした点です:
以下の実践的な考え方を見ていきます:
フレームワークの専門家である必要はありません。目的はメンタルモデルを明確にし、良いReactパターンを見分け、一般的な誤解を避け、React以外でも同じ原則を適用できるようにすることです。
React以前、多くのチームはテンプレート、jQueryスタイルのDOM操作、そして「Xが起きたらYを更新する」というルールの積み重ねでリッチなインターフェースを作っていました。それは小さなUIなら機能しましたが、UIが複雑になると問題が顕在化しました。
よくあるパターンはこうです:データを取得してHTMLをレンダリングし、イベントハンドラを付けてDOMを直接操作する。状態が変わるたびに(新しいアイテム、バリデーションエラー、トグルなど)その依存箇所をすべて覚えておく必要がありました。
その結果、次のようなバグが起きます:
画面が進化するにつれ、同じビジネスルールが複数のハンドラに重複して現れるようになります:「フィールドが空ならボタンを無効化する」「未読アイテムをハイライトする」「結果がなければ空状態を表示する」。要件が変わると、関連のないファイルを何箇所も探して更新しなければなりません。
データは投稿一覧やユーザーオブジェクト、フィルタの集合などいくつかの明確な構造でモデル化できます。UIは読み込み済み vs 読み込み中、エラー vs 成功、既読 vs 未読、編集中 vs 表示中、フィルタ済み vs 未フィルタといった組み合わせを積み重ねるため、複合的になります。
ニュースフィードを想像してください:
「UIは状態の関数である」という予測可能なルールがなければ、多数のDOM編集を調整することになり、競合が起きます。Reactの目的は更新を依存できるものにすることでした:データ/stateを変更すればUIは毎回それに合わせて再レンダーされる、ということです。
コンポーネントは、名前を付けて再利用でき、単独で考えられる小さなUIの単位です。平たく言えば:コンポーネントは入力を受け取り、その入力に対してどんなUIを返すかを示します。
この「入力→出力」というフレーミングがコンポーネントモデルの核心です。画面をひとつの大きなテンプレートとして扱う代わりに、目的を持ったビルディングブロック(ボタン、カード、メニュー、フォーム、セクション全体)に分割して、それらを組み立てます。
Reactで最も一般的な入力はprops(プロパティの略)です。propsはコンポーネントを設定するために渡す値:テキスト、数値、フラグ、イベントハンドラ、あるいは他のUIそのものです。
出力はコンポーネントがレンダリングするUIです。propsが変わればコンポーネントは別の出力を返すことができ、開発者がDOMのどこを更新するかを手動で探す必要がなくなります。
例えば、Buttonコンポーネントはlabel、disabled、onClickのようなpropsを受け取るかもしれません。UserCardはname、avatarUrl、statusを受け取るでしょう。コンポーネントのインターフェース(props)を読むことは、「このUIが正しくレンダリングするために何が必要か」を製品仕様のように読むことと同じです。
UIをコンポーネントに分割する利点はすぐに現れます:
Modal、Input、Dropdownを複数ページで使えるこれはページごとにマークアップをコピーして調整する文化からの大きな転換です。コンポーネントは重複を不必要、そして最終的には容認できないものにします。
Reactは、UIを合成可能な部品として設計することを促します。例:CheckoutPageはOrderSummary、ShippingForm、PaymentMethodを含むツリーになります。各部分は明確な入力と責務を持ちます。
この「まずコンポーネントで考える」シフトがReactがフロントエンドのアーキテクチャを変えた大きな理由の一つです。チームに共通の設計・開発単位(コンポーネント)を与えました。
Reactの最大のメンタルシフトは宣言的UIです:ある状態に対してインターフェースがどうあるべきかを記述すれば、状態が変わったときReactがページの更新を処理します。
要素を探してテキストを書き換え、クラスを切り替え、DOMを同期させる代わりに、UIの「形」に集中します。データが変わるとUIが再記述され、Reactは必要最小限の変更を決定します。
JSXは、JavaScriptの中でHTMLに似た構造を書くための便利な記法です。まったく新しいテンプレート言語を学ぶ必要はなく、「このコンポーネントはこの要素の木をレンダリングする」という省略形と考えればよいです。
利点は、マークアップとそれを決めるロジックが一緒に存在するため、コンポーネントを孤立して理解しやすくなることです。
命令的コードは「どうやって更新するか」をステップごとに書きます:
// Imperative: manually keep the DOM in sync
function setLoggedIn(isLoggedIn) {
const el = document.querySelector('#status');
el.textContent = isLoggedIn ? 'Welcome back' : 'Please sign in';
el.classList.toggle('ok', isLoggedIn);
el.classList.toggle('warn', !isLoggedIn);
}
宣言的コードは、現在の状態に対して「どうあるべきか」を記述します:
function Status({ isLoggedIn }) {
return (
\u003cp className={isLoggedIn ? 'ok' : 'warn'}\u003e
{isLoggedIn ? 'Welcome back' : 'Please sign in'}
\u003c/p\u003e
);
}
レンダリングが純粋な記述として表現されるため、コンポーネントは読みやすく、レビューしやすく、リファクタリングしやすくなります。デザイナー、プロダクト志向のエンジニア、新しいメンバーはしばしばJSXをイベントハンドラやDOM変異を探し回ることなく理解できます。
この明確さによりコラボレーションが向上します:UIの決定は1箇所に見えるようになり、変更が他の部分に隠れた副作用を起こす可能性が減ります。
「状態」は、ユーザーが操作する中で変わり得て、画面がその変化を反映すべきデータです。検索ボックスの現在のテキスト、メニューが開いているか、カート内のアイテム、ネットワークリクエストの結果などが例です。画面がそれを反映するべきなら、それはstateです。
Reactの重要な動きは、レンダリングを一連の手作業ではなく状態の帰結として扱うことです。ある状態に対してUIがどうあるべきかを記述します。状態が更新されると、Reactは関連する部分を再レンダーします。
このメンタルモデルは「要素を探してテキストを更新し、クラスを切り替える」といった流れとは異なります。代わりに状態を更新すれば、UIはその状態から導出されるので自然に更新されます。
単方向データフローはデータが一方向に流れることを意味します:
これにより更新の経路を追いやすくなります:イベントが起き、状態が1箇所で変わり、新しい状態からUIが再描写される。"誰がこの値を変えたのか?"という曖昧さが減ります。
function Counter() {
const [count, setCount] = React.useState(0);
return (
\u003cdiv\u003e
\u003cp\u003eCount: {count}\u003c/p\u003e
\u003cbutton onClick={() =\u003e setCount(count + 1)}\u003eAdd\u003c/button\u003e
\u003c/div\u003e
);
}
ここではcountがstateです。ボタンをクリックするとsetCountでstateが更新され、Reactが再レンダーして段落に新しい数が表示されます。DOMを直接編集する必要はありません。
同じパターンはリストのフィルタ(state = フィルタテキスト、UI = フィルタ済みアイテム)やフォームバリデーション(state = フィールド値とエラー、UI = メッセージ)にもスケールします。データが先、ビューはその結果です。
Reactの核心は「ページをより速く描画する」ことではありません。むしろ重要なのは:UIを状態の結果として扱い、状態が変わったときに「今欲しいUI」と「以前のUI」を比較し、実際に変わった箇所だけを更新することです。
コンポーネントのstateやpropsが変わると、Reactはコンポーネントを再び呼び出して新しいUIの記述を作ります。イメージとしては二つのスナップショットを取るようなものです:
ReactはDOMをクリアして再構築する代わりに、AからBへ移行するために必要な最小のDOM操作を計算しようとします。
仮想DOMとは、Reactが持つメモリ内の軽量なUI表現—画面上にあるべき要素とコンポーネントの出力の木です。これは第二のブラウザでも高速化の魔法でもありません。Reactが効率的に調べて比較できるデータ構造です。
リコンシリエーションは、前の仮想ツリーと次の仮想ツリーの間で何が変わったかを判断するプロセスです。Reactは高速に行うためのヒューリスティックを使います。例:
<div>は<span>ではない)Reactが変化を把握すると、実際のDOMに対して対象を絞った更新を適用します。
これは魔法ではありません。パフォーマンスはパターンに依存します:安定したキー、不要な再レンダーを避ける、コンポーネントの仕事を小さく保つ、レンダリング中に高価な計算をしないこと。ReactはDOMの無駄な変更を減らせますが、アプリの滑らかさはコンポーネント構造とデータフローに依存します。
Reactの最大のスケーリングトリックは特別な機能ではなく、コンポジションです:コンポーネントを入れ子にして画面を構築し、propsでデータを渡し、childrenでコンポーネントが他のUIを包めるようにすることです。
チームがコンポジションを採用すると、ページ単位の発想をやめ、小さく信頼できる部品を組み替えて再実装不要で画面を作るようになります。
children入れ子はUI構造の視覚的表現です:ページはセクションを含み、セクションはカードを含み、カードはボタンを含みます。propsは設定用のノブ(テキスト、状態、コールバック)です。childrenは構造を提供しつつ、呼び出し元に中身を決めさせるための手段です。
良いメンタルモデル:propsはカスタマイズする、childrenは中身を埋める、入れ子で組み立てる。
レイアウトコンポーネントは構造と間隔を定義し、ビジネスロジックを持ちません。例:Page、SidebarLayout、Stack、Modal。多くはchildrenに依存し、同じレイアウトで様々な画面を包めます。
再利用可能な入力コンポーネントはフォームの振る舞いとスタイルを標準化します:TextField、Select、DatePicker。ラベル、エラーステート、バリデーションを画面ごとにコピーする代わりに中央化します。
リストとアイテムの分割は繰り返しUIを予測可能に保ちます。一般的な分離はItemList(取得、ページネーション、空状態)とItemRow(1件の見た目)です。これによりレンダリングの変更がデータ処理を壊しにくくなります。
フック(hooks)は、トグル、フォーム状態、フェッチなどの状態を伴う振る舞いをUIの形を強制せずに再利用する現代的な手段です。この分離によりデザインを進化させつつロジックを一貫させられます。
コンポジションはデザインシステムを一貫性あるものにします:コンポーネントが「承認された」ビルディングブロックとなり、レイアウトが間隔や階層のルールを定義します。システムが更新されると(色、タイポグラフィ、インタラクション状態など)、製品は手作業の修正をほとんどせずに改善を受け継げます。
状態は単に「変わりうるデータ」です。Reactでは、その状態がどこにあるかが状態そのものと同じくらい重要です。
ローカルstateは単一コンポーネント(小さなウィジェット)に属し、他で読む必要がないものに適しています。例:ドロップダウンが開いているか、入力の現在値、選択中のタブ。状態をローカルに置くと調整が減り、コンポーネントは再利用しやすくなります。ルール:一つのコンポーネントしか関心を持たないなら、それをアプリ全体に公開しないでください。
共有状態はUIの複数箇所が同じ値に合意する必要があるデータです。一般的な例:
複数箇所で同じ情報が必要になると、状態を複製すると不整合(ヘッダーは3件、カートページは2件)につながります。
Stateを上げる(Lift state up):共通の親にstateを移し、propsで渡す。シンプルでデータフローが明示的です。
Context:多くのコンポーネントが値を必要としプロップドリリングを避けたいときに有効。テーマや認証など比較的安定したアプリ全体の関心事に向きます。
外部ストア:状態が複雑(頻繁な更新、導出データ、ページ間のワークフロー)になったら専用のストアでロジックと更新を集中させるとよいです。
各状態の所有者を明確にすることが、Reactの単方向データフローの強みを発揮する鍵です。可能な限り単一の真のソースを目指し、合計やフィルタ済みのリストなどは複製せずにそこから導出してください。
Reactの日常的な最大の利点はレンダリングのトリックではなく、コンポーネント境界がUI作業を小さく安全な変更に変える点です。コンポーネントが明確な責務と安定した公開「表面」(props)を持つと、チームは内部をリファクタリングしてもアプリ全体を書き直す必要がなくなります。これによりコードレビューが簡単になり、事故的な壊れを減らし、新しいメンバーがどこを変更すべきか理解しやすくなります。
有用なメンタルモデルはこうです:与えられたpropsとstateに対して、コンポーネントは予測可能にUIを記述するべきだ、ということです。副作用やブラウザAPIは存在しますが、ほとんどのロジックは決定論的に保てます。保守しやすいReactのテストは、振る舞いと出力に焦点を当てることが多いです:
アクセシビリティチェックはここに自然に当てはまります:rolesやアクセシブル名を使ったテストは、ラベル漏れ、フォーカス状態、意味の一貫性を早期に検出します。リンティングやフォーマット、デザインシステムの利用といった一貫性チェックも、予測可能なコンポーネントは保守しやすいという考えを補強します。
コンポーネントが小さなprop APIを公開し実装詳細を隠すと、複数人が並行して作業できます。一人がスタイルを調整し、別の人がデータ取得を変え、さらに別の人がテストを更新する、といった並列作業が衝突を減らして可能になります。
Reactのパフォーマンスは「Reactが遅い」という話ではなく、アプリがブラウザにどれだけの仕事を要求するかに依存します。最速のUIは最小の仕事しかしないUIです:DOMノードが少ない、レイアウト/リフローが少ない、高価な計算が少ない、ネットワーク往復が少ないこと。
よくある問題は不要な再レンダーです:小さな状態変化が大きなツリー全体を再レンダーさせるのは、状態が高いレイヤーにあるか、propsの参照が毎回変わる(インラインで新しいオブジェクト/関数を作る)ときです。
もう一つの古典的な痛点は重いリストです—何百、何千もの行に画像やフォーマットやイベントハンドラがある場合、それぞれが“安い”としても合計で仕事が増え、スクロールがカクつきます。
構造から始めましょう:
memoなどのメモ化を使って、同じ入力で何度も再計算しないようにするまた、ユーザーが体感する部分に集中してください:入力遅延を減らし、ファーストミーニングフルペイントを早め、相互作用を滑らかに保つこと。頻繁に使われる操作での20msの改善は、めったに使われない画面での200msの短縮より価値があることがあります。
派生状態は他のstate/propsから計算できるデータです(例:firstName+lastNameからのfullName、リストとクエリからのフィルタ済みアイテム)。これを保存すると二重の真実が生まれ、ドリフトが起きやすくなります。
派生値はレンダリング中に計算するか(高価ならメモ化する)、保存は本当に導出できないデータのみ—通常はユーザー入力、サーバー応答、UIの意図—に限定してください。
Reactは単にUIの書き方を変えただけではなく、チームがフロントエンドを構築・共有・保守する方法を再編成するきっかけになりました。コンポーネントが標準的なメンタルモデルになる前は、多くのプロジェクトが散在するスクリプトとテンプレートでUIを扱っていました。Reactによりアーキテクチャの単位はますますコンポーネントになりました:明確なAPI(props)と予測できる振る舞いを持つUIの断片です。
Reactはシングルページアプリケーション(SPA)の台頭にうまくフィットしました。レンダリングが状態によって駆動されると、「ページ」はサーバーから配られるテンプレートでなく、コンポーネントの合成とクライアント側ルーティングになります。この変化により、コードは個別のHTMLファイルではなく、機能領域と再利用可能なUIパーツを中心に構成されることが一般的になりました。
UIが再利用可能な部品から構築されると、それらを標準化するのは自然な流れです。多くの組織はマークアップのコピペから、ボタン、フォームコントロール、モーダル、レイアウトプリミティブ、空状態などのパターンを含むコンポーネントライブラリへと移行しました。やがてこれらはデザインシステム(共有コンポーネント+ガイドライン)へと発展し、チームは画面ごとにUIを再発明せずに一貫した体験を提供できるようになりました。
コンポーネントはチームが同じ名前で物事を呼ぶことを促します。みんなが<Button>、<Tooltip>、<CheckoutSummary>のような名前で話すと、会話は見た目だけでなく振る舞いや境界について具体的になります。その共有語彙はコードを通じてシステムを発見しやすくし、新メンバーのオンボーディングも速めます。
Reactの成功は、コンポーネント優先開発、宣言的レンダリング、予測可能なデータフローという考え方をフロントエンドコミュニティ全体に広めました。他のフレームワークも実装の詳細は異なっても類似したアイデアを取り入れるようになり、これらの実践が実チームでスケールしやすいことが裏付けられました。
Reactは複雑なUIの進化を容易にしましたが、それは「タダ」で手に入るものではありません。トレードオフを事前に理解することで、チームは適切な理由で採用し、カルト的な導入を避けられます。
Reactには学習コストがあります:コンポーネント、フック、state更新や副作用のメンタルモデルは習得に時間がかかります。現代のReactはビルドツール(バンドル、リンティング、TypeScriptは任意だがよく使われる)を前提にすることが多く、セットアップと維持が必要です。さらに、Reactは抽象化レイヤー(コンポーネントライブラリ、ルーティング、データ取得パターン)を導入しますが、これらは便利な反面、何かが壊れたときに複雑さを隠す可能性があります。
「Reactはただのビューだけだ」 理論的にはそうですが、実務ではReactはアーキテクチャに強く影響します。コンポーネント境界、状態の所有権、コンポジションパターンはデータフローやコードの組織に影響します。
「仮想DOMは常に速い」 仮想DOMは主に予測可能な更新と開発者の作業性に関するものです。Reactは高速になり得ますが、パフォーマンスはレンダリングパターン、メモ化、リストサイズ、不要な再レンダーを避けることに依存します。
Reactはインタラクティブな状態が多く、長く使われるコードベースで、複数の開発者が並行作業するようなアプリに向いています。静的なマーケティングサイトや少数の小さなウィジェットなら、サーバーサイドテンプレートや軽量なJS、最小限のフレームワークの方が素早く実装・維持できる場合があります。
もしReactアプリのプロトタイプを素早く検証したいなら、コンポーネント境界やstateの所有、コンポジションパターンを試すワークフローが有効です。たとえば、Koder.aiのようなツールはチャットで機能を記述してReactフロントエンド+Go/PostgreSQLバックエンドの実装を生成し、スナップショット/ロールバックで反復し、準備ができたらソースコードをエクスポートできます。実機能でアーキテクチャを試してから本格導入するのは現実的な方法です。
次のステップ:実際の機能を1つプロトタイプし、複雑さとチームの生産性を測定してから、パターンを意図的にスケールしてください(デフォルトで拡大するのではなく)。
Jordan WalkeはFacebook(当時)でReactを作ったエンジニアです。Reactが重要だったのは、壊れやすい手作業のDOM“グルーコード”を、コンポーネントベースで状態駆動のUI構築に置き換えた点にあります。結果として、複雑なインターフェースがスケールしやすく、デバッグや保守がしやすくなりました。
テンプレート+jQueryスタイルのフロントエンドでは、UIルールがマークアップと散在するイベントハンドラに広がりやすく(「Xが起きたらYを更新する」)、変更時に多くの場所を修正する必要がありました。コンポーネントはUIとロジックを小さなインターフェース(props)でまとめるので、ページをパッチする代わりに予測可能な部品から機能を組み立てられます。
コンポーネントは、入力(通常はprops)を受け取り、その入力に対して「どんなUIを返すか」を決める再利用可能な単位です。
実務的には:
Propsはコンポーネントに渡す入力(テキスト、フラグ、コールバック、他のUIなど)です。propsは契約のように扱いましょう:
disabledやonSubmitのような明確な名前を使う宣言的UIとは、現在の状態に対して「インターフェースがどうあるべきか」を記述し、DOMを一歩ずつ更新する方法ではなくその結果をReactに任せる考え方です。
実際の流れは:
JSXは、JavaScriptの中でHTMLに似た構造を書ける記法です。レンダリングロジックとそれが制御するマークアップが一緒にあるため、コンポーネントを一つのまとまりとして読みやすく、レビューしやすくなります。
状態(state)とは、ユーザー操作中に変化するデータであり、その変化が画面に反映されるべきものです(入力値、読み込み状態、カートの中身など)。
実用的ルール:真のソースオブトゥルース(ユーザー入力、サーバー応答、UIの意図)を保存し、派生値(合計、フィルタ済みリストなど)はレンダリング時に算出してください。
単方向データフローとは:
これにより、デバッグは「イベント → 状態変更 → 再レンダー」という直線的な追跡が可能になり、意図しない副作用が減ります。
仮想DOMはReactのメモリ内表現です。stateやpropsが変わると、前のUI記述と新しいUI記述を比較して、実際のDOMを必要な箇所だけ更新します。
よくある注意点:
keyを使う状態の位置を決める基本:まずは単純に始め、必要になったら広げるべきです。
可能な限り単一のソースオブトゥルースを保ち、派生値は算出により得るのが望ましいです。