ReactはコンポーネントベースのUI、宣言的レンダリング、状態駆動のビューを普及させ、チームをページ中心のコードから再利用可能なシステムとパターンへと移行させました。

Reactは単なる新しいライブラリを導入しただけではなく、チームが「フロントエンドアーキテクチャ」と言うときの意味を変えました。実務的には、フロントエンドアーキテクチャとはUIコードベースをスケールして分かりやすく保つための一連の決定です: UIをどのように分割するか、データがどう流れるか、状態はどこに置くか、サイドエフェクト(データ取得など)をどう扱うか、そして結果をどうテスト可能でチーム間で一貫させるか、などです。
コンポーネント思考とは、UIのあらゆるピースをレンダリングを所有する小さな再利用可能な単位として扱い、それらを組み合わせてページ全体を構築することです。
Reactが普及する前は、多くのプロジェクトがページやDOM操作を中心に組織されていました: 「この要素を見つけてテキストを変え、このクラスを切り替える」といった具合です。Reactはチームを別のデフォルトへと押しやりました:
これらの考えは日々の作業を変えました。コードレビューで「どこがこの状態の所有者か?」と問うようになり、デザイナーとエンジニアは共通のコンポーネント語彙で合意でき、チームはページ全体を書き直すことなくUIビルディングブロックのライブラリを育てられるようになりました。
たとえチームが後に別のフレームワークに移行しても、Reactが作った習慣は残ります: コンポーネントベースのアーキテクチャ、宣言的レンダリング、予測可能なデータフロー、そしてワンオフのページコードより再利用可能なデザインシステムコンポーネントを好む文化。Reactはこれらのパターンを普通に感じさせ、フロントエンドの広いエコシステムに影響を与えました。
React以前、多くのチームはページを中心にインターフェースを構築していました。よくある構成はサーバー側でレンダリングされるテンプレート(PHP、Rails、Django、JSP等)にHTMLを出力し、その上にjQueryでインタラクティブ性を付けるやり方です。
ページをレンダリングしてからスクリプトで“アクティブ化”します: 日付ピッカー、モーダルプラグイン、フォームバリデータ、カルーセルなど、それぞれが固有のマークアップ期待値やイベントフックを持ちます。
コードはしばしば「DOMノードを見つけてハンドラを付け、DOMを変更する」という形で、UIが大きくなるにつれて“真の情報源”はいつの間にかDOM自体になっていきました。
UIの振る舞いは一箇所にまとまっていることが稀で、次のように分散していました:
単一ウィジェット(例: チェックアウトサマリ)はサーバーで部分的に組み立てられ、AJAXで部分更新され、プラグインで制御される、というように分散していることがありました。
このアプローチは小さな拡張には有効でしたが、繰り返し発生する問題を生みました:
Backbone、AngularJS、Emberのようなフレームワークはモデル、ビュー、ルーティングで構造をもたらそうとしましたが、多くのチームは依然としてパターンを混在させ、UIを繰り返し使える単位として構築するためのよりシンプルな方法に対するギャップが残っていました。
Reactの最も重要な転換はシンプルに言うと力強いものです: UIは状態の関数だ、ということ。DOMを“真の情報源”として手動で同期させる代わりに、データを真の情報源と扱い、UIはその結果として描かれるものとするのです。
状態とは画面が依存する現在のデータです: メニューが開いているか、フォームに何が入力されているか、リストにどのアイテムがあるか、どのフィルタが選ばれているか、など。
状態が変わるとき、ページ内を探し回って複数のDOMノードを更新する必要はありません。状態を更新すれば、UIはそれに合わせて再レンダリングされます。
従来のDOM第一のコードはしばしば散在する更新ロジックを生みます:
Reactのモデルでは、これらの「更新」はレンダー出力の条件になります。画面はある状態に対して見えるべきものの読みやすい記述になるのです。それによりスクリーンの理解、テスト、進化が容易になります。
空メッセージ、ボタンの無効化状態、リストの内容がすべて items と text から導かれていることに注目してください。これがアーキテクチャ上の利点です: データの形とUI構造が一致し、画面が分かりやすく、テストしやすく、進化させやすくなります。
Reactは「コンポーネント」をデフォルトのUI作業単位にしました: マークアップ、振る舞い、スタイリングのフックを明確なインターフェースで束ねた小さな再利用可能なピースです。
HTMLテンプレートやイベントリスナ、CSSセレクタを無関係なファイルに散らす代わりに、コンポーネントは変わるものを近くにまとめます。すべてを一つのファイルに入れる必要はありませんが、ユーザーが見るものと行うことを中心にコードが整理されるようになります。
実用的なコンポーネントは通常次を含みます:
重要な転換は「このdivを更新する」と考えるのをやめ、「この状態のときボタンを無効化してレンダリングする」と考えることです。
コンポーネントが少数のprops(入力)とイベント/コールバック(出力)を公開すると、その内部を変えてもアプリの他部分を壊しにくくなります。チームは特定のコンポーネントやフォルダ(例えば「checkout UI」)を所有して自信を持って改善できます。
カプセル化は偶発的な結合も減らします: グローバルなセレクタやファイル間の副作用、そして「なぜこのクリックハンドラが動かなくなった?」という驚きが減ります。
コンポーネントが主要なビルディングブロックになると、コードはプロダクトに対応するようになります:
この対応によりUIの議論がしやすくなり、デザイナー、PM、エンジニアが同じ「もの」について話せるようになります。
コンポーネント思考は多くのコードベースを機能・ドメインベースの構成(例: /checkout/components/CheckoutForm)や共有UIライブラリ(一般に /ui/Button)へと押しやりました。機能が増えてもページのみのフォルダ構成よりスケールしやすく、後のデザインシステム構築の土台を作ります。
Reactのレンダリングスタイルはしばしば“宣言的”と表現されます。つまり、ある状況でUIがどうあるべきかを記述すれば、Reactがブラウザをそれに合わせる方法を決めてくれる、ということです。
古いDOM第一のアプローチでは通常手順を書きます:
宣言的レンダリングでは結果を表現します:
ユーザーがログインしていれば名前を表示し、していなければ「サインイン」ボタンを表示する。
この転換により「UIの帳簿付け」が減ります。存在する要素や更新すべきものを常に追跡するのではなく、アプリが取りうる状態に集中できます。
JSXは、テンプレートファイルとロジックファイルを分けずに、構造のようなマークアップをロジックと近くに書ける便利な方法です。条件、小さなフォーマットの決定、イベントハンドラなどを一箇所に置けるため、コンポーネントモデルが実用的に感じられました。
JSXはHTMLとJSを混ぜているように見えますが、JSXは実際にはJavaScript呼び出しを生成する構文です。重要なのは技術そのものを混ぜるのではなく、一緒に変わるものをグループ化している点です。
ロジックとUI構造が密接に結びついている場合(例: 「バリデーションが失敗したときだけエラーメッセージを表示する」)、それらを一箇所にまとめる方が、分散させるより明確なことが多いです。
JSXはReactを親しみやすくしましたが、基になる概念はJSXを超えて広がります。JSXなしでもReactは書けますし、他のフレームワークも異なるテンプレート構文で宣言的レンダリングを採用しています。
持続的な影響はマインドセットです: UIを状態の関数として扱い、フレームワークに画面同期の手順を任せる、という考え方です。
React以前は、データが変わってもUIが追随しないというバグがよくありました。開発者は新しいデータを取得してから、どのDOMノードを更新するかを手動で探し、テキストを更新し、クラスを切り替え、要素を追加/削除して、あらゆるケースで一貫性を保つ必要がありました。時間が経つにつれ「更新ロジック」はUI自体より複雑になりがちでした。
Reactのワークフロー上の大きな変化は、ブラウザにページをどう変えるか指示するのではなく、ある状態でUIがどうあるべきかを記述する点にあります。Reactが実際のDOMと一致させるための差分を計算します。
リコンシリエーションは、前回のレンダーと今回のレンダーを比較し、ブラウザDOMに最小の変更セットを適用するプロセスです。
重要なのは、Reactが「仮想DOMを使って魔法的に高速にしている」ということではなく、予測可能なモデルを提供する点です:
その予測可能さにより、手動のDOM更新が減り、不整合な状態が減り、アプリ全体で同じルールに従ったUI更新が期待できます。
リストをレンダリングする際、Reactは古い項目と新しい項目を対応付ける安定した方法を必要とします。これが key の役割です。
{todos.map(todo => (
<TodoItem key={todo.id} todo={todo} />
))}
IDのような安定かつ一意なキーを使ってください。配列のインデックスは、項目が並べ替えられたり挿入・削除される可能性がある場合には避けてください。さもないとReactが誤ったコンポーネントインスタンスを再利用し、入力の値がおかしくなるなどの驚くべき挙動が発生します。
Reactの大きな建築上の転換の一つは、データが一方向に流れるという点です: 親から子へ、propsを通して。UIのどの部分も他の部分に“手を伸ばして”共有状態を直接変更するのではなく、更新は明示的なイベントとして上に移動し、その結果のデータが下に渡されます。
親が状態を所有し、それを子にpropsとして渡します。子は変更を要求するためにコールバックを呼びます。
function Parent() {
const [count, setCount] = .();
(
);
}
() {
(
);
}
注目すべきは Counter が count を直接変更しない点です。Counter は value(データ)と onIncrement(変更を要求する手段)を受け取ります。この分離がメンタルモデルの核です。
このパターンにより「このデータの所有者は誰か?」が明確になります: 通常は「最近共通の親」が答えです。何かが予期せず変わったとき、状態がどこにあるかをたどればよく、隠れたミューテーションの迷路を追いかける必要が減ります。
この区別はチームがロジックの場所を決めるのに役立ち、偶発的な結合を防ぎます。
propsに依存するコンポーネントは、グローバル変数やDOMクエリに依存しないため再利用しやすく、特定のpropsでレンダリングして結果をアサートすることでテストも簡単になります。状態を持つ振る舞いは状態を管理する箇所でテストします。
Reactはチームを「UIのためにクラス階層を作る」よりも小さくフォーカスされた部品を組み合わせる方向に促しました。基底の Button を継承して10種類のバリエーションを作る代わりに、コンポジションで振る舞いや見た目を組み合わせるほうが一般に簡単です。
よくあるパターンは、データについては何も知らないレイアウトコンポーネントを作ることです:
PageShell(ヘッダー/サイドバー/フッター)Stack / Grid(間隔と配置)Card(一貫した枠組み)これらは children を受け取り、ページ側が中身を決めます。RequireAuth や ErrorBoundary のような軽量なラッパーも、包んでいるものに対して懸念を追加するパターンとしてよく使われます。
children では足りない場合は title、footer、renderRow のようなスロット的propsを使うことで、コンポーネントの柔軟性を保ちながらAPIの爆発を抑えられます。
深い継承ツリーは良い意図から始まりますが、管理が難しくなります:
Hooksはコンポジションをさらに実用的にしました。useDebouncedValue や usePermissions のようなカスタムフックは、UIを共有せずにロジックを共有する手段を提供します。これを共有のUIプリミティブ(ボタン、入力、タイポグラフィ)と組み合わせると、アプリが成長しても理解しやすい再利用が得られます。
Reactはローカルなコンポーネント状態から始めるのを自然にしました: フォームの値、ドロップダウンの開閉、ローディングスピナーなど。それは多くの場合うまく機能しますが、アプリが大きくなると複数箇所のUIが同期を要する場面が増えます。
機能が拡張されると、状態は直接の親子関係にないコンポーネントから読み書きされる必要が出てきます。単にpropsを渡すだけだと、データに関心のないコンポーネントを通して長いpropsチェーンが生じ、リファクタが危険になり、ボイラープレートが増え、結果として同じ「状態」を二箇所が別々に表現してしまうバグが生まれます。
最近共通の親に状態を移し、propsで下に渡す。単純で依存関係が明確になりますが、使いすぎると“ゴッドコンポーネント”を生みます。
テーマやロケール、現在ユーザーのように多くのコンポーネントが同じ値を必要とする場合、React Contextがprop drillingを減らします。ただし頻繁に変わるデータをContextに置くと更新やパフォーマンスの把握が難しくなることがあります。
アプリが大きくなると、Reduxなどのストアパターンが登場しました。これらは状態更新を中央集権化し、アクションやセレクタに関する慣習を提供して、スケール時の予測可能性を高めます。
まずはローカル状態を優先し、兄弟間で調整が必要ならリフトアップ、横断的な関心事にはContext、遠く離れた多くのコンポーネントが同じデータに依存する場合は外部ストアを検討します。適切な選択は流行ではなくアプリの複雑さやチームの規模、要求の変化頻度に依存します。
Reactは単に新しいUIの書き方を導入しただけでなく、コンポーネント駆動のワークフローをチーム文化に定着させました。コード、スタイリング、振る舞いを小さくテスト可能な単位として開発する流れが生まれ、これがプロジェクトの構築や検証、ドキュメント化、デリバリの方法に影響を与えました。
UIがコンポーネントで構成されると「端から内側へ」作るのが自然になります: ボタンを作り、フォームを作り、ページを作る。チームはコンポーネントを製品として扱い、明確なAPI(props)、予測可能な状態(loading、empty、error)、再利用可能なスタイルルールとして管理します。
実務的な変化として、デザイナーと開発者は共通のコンポーネント在庫で整合し、単体で振る舞いをレビューでき、ページレベルでの最後の驚きを減らせます。
Reactの普及は多くのチームにモダンなツール群を定着させました:
たとえ同じツールを選ばなくても期待は同じです: ReactアプリにはUI回帰を早期に検出するガードレールが必要だということです。
最近では、チャット主導の計画フローからReactフロントエンド(とその周辺のバックエンド)をスキャフォールドするようなvibe-codingプラットフォーム(例: Koder.ai)を使い、コンポーネント構造や状態の所有、機能境界を短時間で検証してから手作業の配線に移るチームもあります。
Reactチームはコンポーネントエクスプローラ(コンポーネントをさまざまな状態でレンダリングし、ノートを付け、使用ガイドを共有する専用環境)の考え方を普及させました。
このStorybookスタイルの考え方はコラボレーションを変えます: コンポーネントがページに組み込まれる前に振る舞いをレビューでき、エッジケースを手動QAで見つかるのを待つのではなく意図的に検証できます。
再利用ライブラリを作るなら、これはデザインシステムのアプローチと自然に結びつきます—詳細は /blog/design-systems-basics を参照してください。
コンポーネントベースのツールは、より小さなプルリクエスト、明確なビジュアルレビュー、安全なリファクタを促します。時間が経つにつれ、チームはページ全体の複雑なDOMコードを掻き分けるより、よくスコープされた部分で反復する方が速くUI変更を出せるようになります。
デザインシステムは実務的には二つの要素が結びついたものです: 再利用可能なUIコンポーネントのライブラリ(ボタン、フォーム、モーダル、ナビゲーション)と、それらの使い方を説明するガイドライン(間隔、タイポグラフィ、トーン、アクセシビリティルール、インタラクションパターン)。
Reactは「コンポーネント」がコアの単位であるため、このアプローチを自然にしました。マークアップをページ間でコピーする代わりに <Button /> や <TextField />、<Dialog /> を一度公開してプロジェクト内で再利用でき、propsによる制御で安全にカスタマイズできます。
Reactコンポーネントは自己完結的で、構造、振る舞い、スタイルを安定したインターフェースの背後に束ねられます。これによりライブラリは:
新規に始めるなら、コンポーネント群が一貫性のないゴミの山とならないようにするチェックリストが有効です: /blog/component-library-checklist を参照してください。
デザインシステムは見た目の一貫性だけではなく、振る舞いの一貫性でもあります。モーダルが常にフォーカスを閉じ込める、ドロップダウンがキーボード操作をサポートする、などがデフォルトになるとアクセシビリティは後回しではなく標準になります。
テーマも容易になります: カラートークン、間隔、タイポグラフィを中央で管理し、コンポーネントに消費させればブランド変更が全画面に波及することはありません。
共有コンポーネントに投資する価値があるかどうかの評価は、しばしばスケールと保守コストに結びつきます。組織によってはその評価を /pricing のようなプラットフォーム計画に結び付ける場合もあります。
Reactは単にUIの作り方を変えただけでなく、品質の評価方法も変えました。アプリが明確な入力(props)と出力(レンダリングされたUI)を持つコンポーネントで構成されると、テストとパフォーマンスは後付けの修正ではなく設計上の決定になります。
コンポーネント境界により次の二つのレベルでテストできます:
これは最も効果的に機能するのはコンポーネントが所有権を明確に持っている場合です: 状態を一箇所で所有し、子は主に表示とイベント発火を行う。
Reactアプリが速く感じられる理由の多くは設計段階でパフォーマンスを組み込んでいるからです:
有用なルールは「大きなコストのかかる部分(大きなリスト、複雑な計算、頻繁に再レンダリングされる領域)を最適化する」ことです。細かい勝利に追いかけられすぎないようにしましょう。
時間が経つとチームは次のような罠に陥りがちです: 過剰なコンポーネント分割(目的の不明な小片が増える)、prop drilling(多層に渡るpropsの受け渡し)、状態の所有が曖昧で誰もどのコンポーネントがその状態を“持っている”か分からない状況。
自動生成やスキャフォールディングを多用するとこれらの罠は早く現れます: コンポーネントが増え、所有権が曖昧になるのです。手書きでコーディングする場合もKoder.aiのようにReactアプリとバックエンドを生成するツールを使う場合も、ガードレールは同じです: 状態の所有権を明確にし、コンポーネントAPIを小さく保ち、機能の境界に向けてリファクタリングすること。
Server Components、メタフレームワーク、より良いツール類がReactアプリの配信方法を進化させ続けるでしょう。しかし残る教訓は変わりません: 状態、所有権、合成可能なUIビルディングブロックを中心に設計し、テストとパフォーマンスはその後に自然に従わせることです。
構造に関するより深い決定は /blog/state-management-react を参照してください。
Reactはフロントエンドアーキテクチャをいくつかのコアな決定に基づいて再定義しました:
実務上の効果は、手作業によるDOMの帳簿付けが減り、チームやツールのための境界がより明確になる点です。
コンポーネント思考とは、各UIのピースを小さく再利用可能な単位として扱い、そのレンダリングを所有し、より大きな画面に組み合わせられるようにすることを意味します。実務的には、コンポーネントは次をひとまとめにします:
これにより「このDOMノードを更新する」から「この状態でこのコンポーネントをレンダリングする」へと考え方が移ります。
DOM中心のコードでは、しばしばDOM自体が真の情報源になり、複数の要素を手動で同期させる必要があります。Reactでは状態を更新し、それに基づいてレンダリングするため、ローディングスピナーや無効化ボタン、空状態などが自然に一貫して保たれます。
良い判定基準:多くの「要素を見つけてクラスを切り替える」ようなコードを書いているなら、そのモデルと戦っている可能性が高いです。UIが状態と乖離する場合、多くは状態の所有権の問題です。
React以前は、多くのアプリがページ中心で構築され、サーバー側テンプレート+jQueryやプラグインが混在していました。振る舞いはサーバービュー、HTML属性、JSの初期化コードに分散していました。
よくある問題点:
Reactはチームを再利用可能なコンポーネントと予測可能な更新へと導きました。
宣言的レンダリングとは、DOMをどう変えるか手順で書くのではなく、ある状態でUIがどうあるべきかを表現することです。
従来の手順的コード:
宣言的レンダリングではレンダリングの出力に条件を表現します(例:「ログインしていれば名前を表示、していなければサインインボタンを表示」)。Reactが実際のDOMを整合させます。
JSXは、UI構造をそれを制御するロジックの近くに書けるようにしたため普及を助けました(条件、フォーマット、ハンドラなど)。これによりテンプレートとロジックを行き来する必要が減ります。
JSXはHTMLそのものではなくJavaScript呼び出しを生成します。重要なのは、変化するものを一箇所にまとめるという組織上の利点で、UIと振る舞いを一緒に置くほうが管理しやすい場合が多いという点です。
リコンシリエーションは、前回のレンダー結果と今回のレンダー結果を比較し、最小限のDOM差分を適用するプロセスです。
ポイントは「仮想DOMが魔法のパフォーマンストリックだ」という誤解ではなく、予測可能なモデルを提供する点です:
リストをレンダリングするとき、Reactは古いアイテムと新しいアイテムを一致させる安定した方法が必要です。それが key の役割です。
一方向データフローは、データが親から子へと渡され、子はコールバックで変更を要求するという考え方です。
例: 親が状態を所有し、それを子に props として渡す。子は onIncrement のようなコールバックを呼んで変更を要求します。
継承ではなくコンポジションという考え方は、UIを小さくフォーカスされた部品から組み立てることを奨励します。多様なバリエーションのために基底クラスを深く継承するより、コンポーネントを組み合わせる方が柔軟で理解しやすいです。
日常的なパターン:
PageShell(ヘッダ/サイドバー/フッタ)Stack / Grid(余白・配置)Card(一貫したフレーミング)これらは を受け取り、ページ側が中身を決めます。 で足りないときは や 、 のようなスロット的なpropsを使います。
Reactではまずローカルなコンポーネント状態から始めるのが自然です(フォーム入力、ドロップダウンの開閉、ローディングなど)。しかしアプリが大きくなると、状態を複数の遠いコンポーネントで共有する必要が出てきます。
一般的な進め方:
選択は流行ではなく、アプリの複雑さやチームのニーズで決めるべきです。
function ShoppingList() {
const [items, setItems] = useState([]);
const [text, setText] = useState("");
const add = () => setItems([...items, text.trim()]).then(() => setText(""));
return (
<section>
<form onSubmit={(e) => { e.preventDefault(); add(); }}>
<input value={text} onChange={(e) => setText(e.target.value)} />
<button disabled={!text.trim()}>Add</button>
</form>
{items.length === 0 ? <p>No items yet.</p> : (
<ul>{items.map((x, i) => <li key={i}>{x}</li>)}</ul>
)}
</section>
);
}
{todos.map(todo => (
<TodoItem key={todo.id} todo={todo} />
))}
key は安定かつ一意な値(IDなど)を使い、要素が並べ替え・挿入・削除されうる場合は配列のインデックスを避けてください。でないとコンポーネントインスタンスが誤って再利用され、入力値が入れ替わるなどの意外な挙動が起きます。
function Parent() {
const [count, setCount] = React.useState(0);
return (
<Counter
value={count}
onIncrement={() => setCount(c => c + 1)}
/>
);
}
function Counter({ value, onIncrement }) {
return (
<button onClick={onIncrement}>
Clicks: {value}
</button>
);
}
このパターンは「このデータの所有者は誰か?」を明確にし、デバッグ時に「状態がどこにあるか」を追えばよく、隠れた変更の迷路を追いかける必要が減ります。
childrenchildrentitlefooterrenderRowHooks(カスタムフック)はUIを共有せずロジックを共有する方法として有効で、useDebouncedValue や usePermissions のような再利用の手段を提供します。