1 分

Reactがコンポーネントで再定義したフロントエンドアーキテクチャ

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

Reactがコンポーネントで再定義したフロントエンドアーキテクチャ

Reactが到来して何が変わったのか

Reactは単なる新しいライブラリを導入しただけではなく、チームが「フロントエンドアーキテクチャ」と言うときの意味を変えました。実務的には、フロントエンドアーキテクチャとはUIコードベースをスケールして分かりやすく保つための一連の決定です: UIをどのように分割するか、データがどう流れるか、状態はどこに置くか、サイドエフェクト(データ取得など)をどう扱うか、そして結果をどうテスト可能でチーム間で一貫させるか、などです。

一言で言うコンポーネント思考

コンポーネント思考とは、UIのあらゆるピースをレンダリングを所有する小さな再利用可能な単位として扱い、それらを組み合わせてページ全体を構築することです。

Reactが促した変化

Reactが普及する前は、多くのプロジェクトがページやDOM操作を中心に組織されていました: 「この要素を見つけてテキストを変え、このクラスを切り替える」といった具合です。Reactはチームを別のデフォルトへと押しやりました:

  • 状態駆動のUI: 状態を更新すればUIがそれに従って更新される。
  • 配線よりもコンポジション: スクリーンはコンポーネントをネスト・結合して組み立て、振る舞いを無関係なファイル間で継ぎ接ぎしない。
  • 再利用を第一目標に: 共有されるUIパターンはコピーしたマークアップではなくコンポーネントになる。

これらの考えは日々の作業を変えました。コードレビューで「どこがこの状態の所有者か?」と問うようになり、デザイナーとエンジニアは共通のコンポーネント語彙で合意でき、チームはページ全体を書き直すことなくUIビルディングブロックのライブラリを育てられるようになりました。

Reactより大きな影響

たとえチームが後に別のフレームワークに移行しても、Reactが作った習慣は残ります: コンポーネントベースのアーキテクチャ、宣言的レンダリング、予測可能なデータフロー、そしてワンオフのページコードより再利用可能なデザインシステムコンポーネントを好む文化。Reactはこれらのパターンを普通に感じさせ、フロントエンドの広いエコシステムに影響を与えました。

React以前: ページ中心UIとDOM第一のコード

React以前、多くのチームはページを中心にインターフェースを構築していました。よくある構成はサーバー側でレンダリングされるテンプレート(PHP、Rails、Django、JSP等)にHTMLを出力し、その上にjQueryでインタラクティブ性を付けるやり方です。

典型的なスタック: テンプレート + jQuery + プラグイン

ページをレンダリングしてからスクリプトで“アクティブ化”します: 日付ピッカー、モーダルプラグイン、フォームバリデータ、カルーセルなど、それぞれが固有のマークアップ期待値やイベントフックを持ちます。

コードはしばしば「DOMノードを見つけてハンドラを付け、DOMを変更する」という形で、UIが大きくなるにつれて“真の情報源”はいつの間にかDOM自体になっていきました。

振る舞いが層に散らばる

UIの振る舞いは一箇所にまとまっていることが稀で、次のように分散していました:

  • サーバービュー(条件付きレンダリング、パーシャル、機能フラグ)
  • HTML(data-*属性、インラインハンドラ、隠しフィールド)
  • JavaScript(jQueryセレクタ、グローバル状態、プラグインの初期化)

単一ウィジェット(例: チェックアウトサマリ)はサーバーで部分的に組み立てられ、AJAXで部分更新され、プラグインで制御される、というように分散していることがありました。

よくある問題点

このアプローチは小さな拡張には有効でしたが、繰り返し発生する問題を生みました:

  • UIの重複: 同じ「コンポーネント」が複数のテンプレートやページで再構築される
  • 状態の不一致: ローディングスピナーや無効化ボタン、エラーメッセージが同期を失う
  • 脆いDOMコード: 小さなマークアップ変更でセレクタやイベント配線が壊れる

初期のMV*が橋渡しに

Backbone、AngularJS、Emberのようなフレームワークはモデル、ビュー、ルーティングで構造をもたらそうとしましたが、多くのチームは依然としてパターンを混在させ、UIを繰り返し使える単位として構築するためのよりシンプルな方法に対するギャップが残っていました。

大きな考え方: 状態の関数としてのUI

Reactの最も重要な転換はシンプルに言うと力強いものです: UIは状態の関数だ、ということ。DOMを“真の情報源”として手動で同期させる代わりに、データを真の情報源と扱い、UIはその結果として描かれるものとするのです。

日常的なアプリでの「状態」とは

状態とは画面が依存する現在のデータです: メニューが開いているか、フォームに何が入力されているか、リストにどのアイテムがあるか、どのフィルタが選ばれているか、など。

状態が変わるとき、ページ内を探し回って複数のDOMノードを更新する必要はありません。状態を更新すれば、UIはそれに合わせて再レンダリングされます。

なぜ手動のDOM作業が減るのか

従来のDOM第一のコードはしばしば散在する更新ロジックを生みます:

  • チェックボックスが変わる → ラベルを更新する
  • アイテムを追加する → リストと空状態メッセージを更新する
  • フォーム送信 → ボタンを無効化し、スピナーを表示し、エラーを処理する

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>
  );
}

空メッセージ、ボタンの無効化状態、リストの内容がすべて itemstext から導かれていることに注目してください。これがアーキテクチャ上の利点です: データの形とUI構造が一致し、画面が分かりやすく、テストしやすく、進化させやすくなります。

コンポーネントが新しいビルディングブロックに

Reactは「コンポーネント」をデフォルトのUI作業単位にしました: マークアップ、振る舞い、スタイリングのフックを明確なインターフェースで束ねた小さな再利用可能なピースです。

HTMLテンプレートやイベントリスナ、CSSセレクタを無関係なファイルに散らす代わりに、コンポーネントは変わるものを近くにまとめます。すべてを一つのファイルに入れる必要はありませんが、ユーザーが見るものと行うことを中心にコードが整理されるようになります。

コンポーネントとは実際には何か

実用的なコンポーネントは通常次を含みます:

  • 構造(何をレンダリングするか)
  • インタラクション(ハンドラ、状態、エフェクト)
  • スタイリングの入り口(クラス名、バリアント、トークン)

重要な転換は「このdivを更新する」と考えるのをやめ、「この状態のときボタンを無効化してレンダリングする」と考えることです。

カプセル化 = 保守が楽で所有権が明確

コンポーネントが少数のprops(入力)とイベント/コールバック(出力)を公開すると、その内部を変えてもアプリの他部分を壊しにくくなります。チームは特定のコンポーネントやフォルダ(例えば「checkout UI」)を所有して自信を持って改善できます。

カプセル化は偶発的な結合も減らします: グローバルなセレクタやファイル間の副作用、そして「なぜこのクリックハンドラが動かなくなった?」という驚きが減ります。

コンポーネントはプロダクト概念に対応する

コンポーネントが主要なビルディングブロックになると、コードはプロダクトに対応するようになります:

  • Button(primary/secondary、loading、icon)
  • Modal(open/close、フォーカストラップ、Escapeキー)
  • CheckoutForm(バリデーション、送信、エラーステート)

この対応によりUIの議論がしやすくなり、デザイナー、PM、エンジニアが同じ「もの」について話せるようになります。

ファイル構成への影響

コンポーネント思考は多くのコードベースを機能・ドメインベースの構成(例: /checkout/components/CheckoutForm)や共有UIライブラリ(一般に /ui/Button)へと押しやりました。機能が増えてもページのみのフォルダ構成よりスケールしやすく、後のデザインシステム構築の土台を作ります。

宣言的レンダリングとJSXが定着させた理由

Reactのレンダリングスタイルはしばしば“宣言的”と表現されます。つまり、ある状況でUIがどうあるべきかを記述すれば、Reactがブラウザをそれに合わせる方法を決めてくれる、ということです。

「手順ではなく結果を記述する」

古いDOM第一のアプローチでは通常手順を書きます:

  • 要素を見つける
  • 新しいノードを作る
  • テキストを設定する
  • 追加する
  • 後で更新または削除する

宣言的レンダリングでは結果を表現します:

ユーザーがログインしていれば名前を表示し、していなければ「サインイン」ボタンを表示する。

この転換により「UIの帳簿付け」が減ります。存在する要素や更新すべきものを常に追跡するのではなく、アプリが取りうる状態に集中できます。

JSXが普及を助けた理由

JSXは、テンプレートファイルとロジックファイルを分けずに、構造のようなマークアップをロジックと近くに書ける便利な方法です。条件、小さなフォーマットの決定、イベントハンドラなどを一箇所に置けるため、コンポーネントモデルが実用的に感じられました。

「HTMLとJSを混ぜているのでは?」という懸念

JSXはHTMLとJSを混ぜているように見えますが、JSXは実際にはJavaScript呼び出しを生成する構文です。重要なのは技術そのものを混ぜるのではなく、一緒に変わるものをグループ化している点です。

ロジックとUI構造が密接に結びついている場合(例: 「バリデーションが失敗したときだけエラーメッセージを表示する」)、それらを一箇所にまとめる方が、分散させるより明確なことが多いです。

宣言的レンダリングはJSXだけではない

JSXはReactを親しみやすくしましたが、基になる概念はJSXを超えて広がります。JSXなしでもReactは書けますし、他のフレームワークも異なるテンプレート構文で宣言的レンダリングを採用しています。

持続的な影響はマインドセットです: UIを状態の関数として扱い、フレームワークに画面同期の手順を任せる、という考え方です。

リコンシリエーションと仮想DOM(神話を除いて)

本番対応の仕上げをする
ユーザー向けに準備できたら、カスタムドメインを設定しよう。

React以前は、データが変わってもUIが追随しないというバグがよくありました。開発者は新しいデータを取得してから、どのDOMノードを更新するかを手動で探し、テキストを更新し、クラスを切り替え、要素を追加/削除して、あらゆるケースで一貫性を保つ必要がありました。時間が経つにつれ「更新ロジック」はUI自体より複雑になりがちでした。

Reactのワークフロー上の大きな変化は、ブラウザにページをどう変えるか指示するのではなく、ある状態でUIがどうあるべきかを記述する点にあります。Reactが実際のDOMと一致させるための差分を計算します。

「リコンシリエーション」とは何か

リコンシリエーションは、前回のレンダーと今回のレンダーを比較し、ブラウザDOMに最小の変更セットを適用するプロセスです。

重要なのは、Reactが「仮想DOMを使って魔法的に高速にしている」ということではなく、予測可能なモデルを提供する点です:

  • レンダーロジックはまるでUIを最初から再生成するかのように書ける。
  • ReactはインクリメンタルにDOMを更新するので、ユーザーが完全な再描画のコストを負わない。

その予測可能さにより、手動のDOM更新が減り、不整合な状態が減り、アプリ全体で同じルールに従ったUI更新が期待できます。

キー(key)の実務的な取り扱い

リストをレンダリングする際、Reactは古い項目と新しい項目を対応付ける安定した方法を必要とします。これが key の役割です。

{todos.map(todo => (
  <TodoItem key={todo.id} todo={todo} />
))}

IDのような安定かつ一意なキーを使ってください。配列のインデックスは、項目が並べ替えられたり挿入・削除される可能性がある場合には避けてください。さもないとReactが誤ったコンポーネントインスタンスを再利用し、入力の値がおかしくなるなどの驚くべき挙動が発生します。

一方向データフロー: より単純なメンタルモデル

Reactの大きな建築上の転換の一つは、データが一方向に流れるという点です: 親から子へ、propsを通して。UIのどの部分も他の部分に“手を伸ばして”共有状態を直接変更するのではなく、更新は明示的なイベントとして上に移動し、その結果のデータが下に渡されます。

親子の簡単な例

親が状態を所有し、それを子にpropsとして渡します。子は変更を要求するためにコールバックを呼びます。

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>
  );
}

注目すべきは Countercount を直接変更しない点です。Countervalue(データ)と onIncrement(変更を要求する手段)を受け取ります。この分離がメンタルモデルの核です。

境界が明確になり副作用が減る

このパターンにより「このデータの所有者は誰か?」が明確になります: 通常は「最近共通の親」が答えです。何かが予期せず変わったとき、状態がどこにあるかをたどればよく、隠れたミューテーションの迷路を追いかける必要が減ります。

Props と state の整理原則

  • State: コンポーネントの内部の、変更可能なデータ(真の情報源)
  • Props: 外部から渡される入力(受け取り側から見て読み取り専用)

この区別はチームがロジックの場所を決めるのに役立ち、偶発的な結合を防ぎます。

再利用とテストが簡単に

propsに依存するコンポーネントは、グローバル変数やDOMクエリに依存しないため再利用しやすく、特定のpropsでレンダリングして結果をアサートすることでテストも簡単になります。状態を持つ振る舞いは状態を管理する箇所でテストします。

継承よりコンポジション: 実プロジェクトでの適用

アプリの成長に合わせて拡張
チームの開発・運用に合わせて、Free、Pro、Business、Enterpriseから選べる。

Reactはチームを「UIのためにクラス階層を作る」よりも小さくフォーカスされた部品を組み合わせる方向に促しました。基底の Button を継承して10種類のバリエーションを作る代わりに、コンポジションで振る舞いや見た目を組み合わせるほうが一般に簡単です。

日常のコンポジションの例

よくあるパターンは、データについては何も知らないレイアウトコンポーネントを作ることです:

  • PageShell(ヘッダー/サイドバー/フッター)
  • Stack / Grid(間隔と配置)
  • Card(一貫した枠組み)

これらは children を受け取り、ページ側が中身を決めます。RequireAuthErrorBoundary のような軽量なラッパーも、包んでいるものに対して懸念を追加するパターンとしてよく使われます。

children では足りない場合は titlefooterrenderRow のようなスロット的propsを使うことで、コンポーネントの柔軟性を保ちながらAPIの爆発を抑えられます。

継承が陥りがちな問題

深い継承ツリーは良い意図から始まりますが、管理が難しくなります:

  • 振る舞いが複数レベルに分散する(「このスタイルはどこから来ている?」)
  • 基底クラスの変更が無関係な画面に波及する
  • オーバーライドが積み重なり、汎用基底が何でも入るゴミ箱になる

現代のコンポジション手段: Hooks

Hooksはコンポジションをさらに実用的にしました。useDebouncedValueusePermissions のようなカスタムフックは、UIを共有せずにロジックを共有する手段を提供します。これを共有のUIプリミティブ(ボタン、入力、タイポグラフィ)と組み合わせると、アプリが成長しても理解しやすい再利用が得られます。

状態管理: ローカルから共有ストアへ

Reactはローカルなコンポーネント状態から始めるのを自然にしました: フォームの値、ドロップダウンの開閉、ローディングスピナーなど。それは多くの場合うまく機能しますが、アプリが大きくなると複数箇所のUIが同期を要する場面が増えます。

状態共有が難しくなる理由

機能が拡張されると、状態は直接の親子関係にないコンポーネントから読み書きされる必要が出てきます。単にpropsを渡すだけだと、データに関心のないコンポーネントを通して長いpropsチェーンが生じ、リファクタが危険になり、ボイラープレートが増え、結果として同じ「状態」を二箇所が別々に表現してしまうバグが生まれます。

よく使われるアプローチ

  1. 状態をリフトアップする

最近共通の親に状態を移し、propsで下に渡す。単純で依存関係が明確になりますが、使いすぎると“ゴッドコンポーネント”を生みます。

  1. コンテキストをクロスカッティングな懸念に使う

テーマやロケール、現在ユーザーのように多くのコンポーネントが同じ値を必要とする場合、React Contextがprop drillingを減らします。ただし頻繁に変わるデータをContextに置くと更新やパフォーマンスの把握が難しくなることがあります。

  1. 外部ストア

アプリが大きくなると、Reduxなどのストアパターンが登場しました。これらは状態更新を中央集権化し、アクションやセレクタに関する慣習を提供して、スケール時の予測可能性を高めます。

何を選ぶべきか

まずはローカル状態を優先し、兄弟間で調整が必要ならリフトアップ、横断的な関心事にはContext、遠く離れた多くのコンポーネントが同じデータに依存する場合は外部ストアを検討します。適切な選択は流行ではなくアプリの複雑さやチームの規模、要求の変化頻度に依存します。

Reactが普及させたツールとワークフロー

Reactは単に新しいUIの書き方を導入しただけでなく、コンポーネント駆動のワークフローをチーム文化に定着させました。コード、スタイリング、振る舞いを小さくテスト可能な単位として開発する流れが生まれ、これがプロジェクトの構築や検証、ドキュメント化、デリバリの方法に影響を与えました。

コンポーネント駆動開発が日常ワークフローに

UIがコンポーネントで構成されると「端から内側へ」作るのが自然になります: ボタンを作り、フォームを作り、ページを作る。チームはコンポーネントを製品として扱い、明確なAPI(props)、予測可能な状態(loading、empty、error)、再利用可能なスタイルルールとして管理します。

実務的な変化として、デザイナーと開発者は共通のコンポーネント在庫で整合し、単体で振る舞いをレビューでき、ページレベルでの最後の驚きを減らせます。

標準化したツールチェーン

Reactの普及は多くのチームにモダンなツール群を定着させました:

  • 高速なローカルイテレーションのためのバンドラとdevサーバ(ホットリロード、コード分割)
  • 大規模なコンポーネントコードベースを一貫させるリンティングとフォーマッティング
  • コンポーネントのpropsや状態を誤用しにくくする型チェック(多くはTypeScript)
  • テストランナーとコンポーネントにフォーカスしたテスティングユーティリティ

たとえ同じツールを選ばなくても期待は同じです: ReactアプリにはUI回帰を早期に検出するガードレールが必要だということです。

最近では、チャット主導の計画フローからReactフロントエンド(とその周辺のバックエンド)をスキャフォールドするようなvibe-codingプラットフォーム(例: Koder.ai)を使い、コンポーネント構造や状態の所有、機能境界を短時間で検証してから手作業の配線に移るチームもあります。

コンポーネントドキュメントと孤立したプレビュー

Reactチームはコンポーネントエクスプローラ(コンポーネントをさまざまな状態でレンダリングし、ノートを付け、使用ガイドを共有する専用環境)の考え方を普及させました。

このStorybookスタイルの考え方はコラボレーションを変えます: コンポーネントがページに組み込まれる前に振る舞いをレビューでき、エッジケースを手動QAで見つかるのを待つのではなく意図的に検証できます。

再利用ライブラリを作るなら、これはデザインシステムのアプローチと自然に結びつきます—詳細は /blog/design-systems-basics を参照してください。

ワークフローがリリースに与える影響

コンポーネントベースのツールは、より小さなプルリクエスト、明確なビジュアルレビュー、安全なリファクタを促します。時間が経つにつれ、チームはページ全体の複雑なDOMコードを掻き分けるより、よくスコープされた部分で反復する方が速くUI変更を出せるようになります。

デザインシステムと再利用可能なコンポーネントライブラリ

リファクタを恐れずに
リファクタを試し、スナップショットとロールバックで簡単に戻せる。

デザインシステムは実務的には二つの要素が結びついたものです: 再利用可能なUIコンポーネントのライブラリ(ボタン、フォーム、モーダル、ナビゲーション)と、それらの使い方を説明するガイドライン(間隔、タイポグラフィ、トーン、アクセシビリティルール、インタラクションパターン)。

Reactは「コンポーネント」がコアの単位であるため、このアプローチを自然にしました。マークアップをページ間でコピーする代わりに <Button /><TextField /><Dialog /> を一度公開してプロジェクト内で再利用でき、propsによる制御で安全にカスタマイズできます。

なぜReactはUIライブラリに向くのか

Reactコンポーネントは自己完結的で、構造、振る舞い、スタイルを安定したインターフェースの背後に束ねられます。これによりライブラリは:

  • 文書化され、使用例や注意点を持てる
  • バージョン管理でき、段階的に変更をリリースできる
  • 合成可能で小さな部品がより大きなパターンに組み合わさる

新規に始めるなら、コンポーネント群が一貫性のないゴミの山とならないようにするチェックリストが有効です: /blog/component-library-checklist を参照してください。

一貫性の利点: アクセシビリティ、テーマ、共有動作

デザインシステムは見た目の一貫性だけではなく、振る舞いの一貫性でもあります。モーダルが常にフォーカスを閉じ込める、ドロップダウンがキーボード操作をサポートする、などがデフォルトになるとアクセシビリティは後回しではなく標準になります。

テーマも容易になります: カラートークン、間隔、タイポグラフィを中央で管理し、コンポーネントに消費させればブランド変更が全画面に波及することはありません。

共有コンポーネントに投資する価値があるかどうかの評価は、しばしばスケールと保守コストに結びつきます。組織によってはその評価を /pricing のようなプラットフォーム計画に結び付ける場合もあります。

テスト、パフォーマンス、よくあるアーキテクチャの落とし穴

Reactは単にUIの作り方を変えただけでなく、品質の評価方法も変えました。アプリが明確な入力(props)と出力(レンダリングされたUI)を持つコンポーネントで構成されると、テストとパフォーマンスは後付けの修正ではなく設計上の決定になります。

境界が明確だとテストが簡単に

コンポーネント境界により次の二つのレベルでテストできます:

  • ユニットテスト: 特定のpropsやstateでコンポーネントが正しくレンダリングされるか(エッジケース含む)を検証。コンポーネントを小さな「UI関数」として扱える。
  • 統合テスト: フォームとそのバリデーションメッセージ、リストとフィルタなどの小さなツリーをレンダリングし、ユーザー操作が期待どおりのUI変化を生むかを確認する。

これは最も効果的に機能するのはコンポーネントが所有権を明確に持っている場合です: 状態を一箇所で所有し、子は主に表示とイベント発火を行う。

パフォーマンスはマイクロ最適化ではなくアーキテクチャ

Reactアプリが速く感じられる理由の多くは設計段階でパフォーマンスを組み込んでいるからです:

  • コードスプリッティング: ルートや機能に必要なものだけを読み込み、初期読み込みを小さく保つ
  • メモ化: 入力が変わらない場合に不必要な再レンダリングを防ぐ(意図的に使うこと)
  • 遅延読み込み: 重いウィジェット(チャートやエディタ)をユーザーが必要とするまで遅らせる

有用なルールは「大きなコストのかかる部分(大きなリスト、複雑な計算、頻繁に再レンダリングされる領域)を最適化する」ことです。細かい勝利に追いかけられすぎないようにしましょう。

注意すべき落とし穴

時間が経つとチームは次のような罠に陥りがちです: 過剰なコンポーネント分割(目的の不明な小片が増える)、prop drilling(多層に渡るpropsの受け渡し)、状態の所有が曖昧で誰もどのコンポーネントがその状態を“持っている”か分からない状況。

自動生成やスキャフォールディングを多用するとこれらの罠は早く現れます: コンポーネントが増え、所有権が曖昧になるのです。手書きでコーディングする場合もKoder.aiのようにReactアプリとバックエンドを生成するツールを使う場合も、ガードレールは同じです: 状態の所有権を明確にし、コンポーネントAPIを小さく保ち、機能の境界に向けてリファクタリングすること。

次に来るもの(と残るもの)

Server Components、メタフレームワーク、より良いツール類がReactアプリの配信方法を進化させ続けるでしょう。しかし残る教訓は変わりません: 状態、所有権、合成可能なUIビルディングブロックを中心に設計し、テストとパフォーマンスはその後に自然に従わせることです。

構造に関するより深い決定は /blog/state-management-react を参照してください。

よくある質問

Reactの文脈で「フロントエンドアーキテクチャ」とは何を指す?

Reactはフロントエンドアーキテクチャをいくつかのコアな決定に基づいて再定義しました:

  • UIを再利用可能なコンポーネントに分割すること
  • UIを状態駆動にする(データを真の情報源とすること)
  • 画面を構築するためにコンポジションを使うこと
  • 所有権を明確にするために一方向データフローを採用すること

実務上の効果は、手作業によるDOMの帳簿付けが減り、チームやツールのための境界がより明確になる点です。

実用的に見て「コンポーネント思考」とは何?

コンポーネント思考とは、各UIのピースを小さく再利用可能な単位として扱い、そのレンダリングを所有し、より大きな画面に組み合わせられるようにすることを意味します。実務的には、コンポーネントは次をひとまとめにします:

  • 構造(何をレンダリングするか)
  • インタラクション(ハンドラ、状態、エフェクト)
  • スタイリングの入り口(クラス、バリアント、トークン)

これにより「このDOMノードを更新する」から「この状態でこのコンポーネントをレンダリングする」へと考え方が移ります。

なぜReactは手動のDOM操作の必要性を減らしたのか?

DOM中心のコードでは、しばしばDOM自体が真の情報源になり、複数の要素を手動で同期させる必要があります。Reactでは状態を更新し、それに基づいてレンダリングするため、ローディングスピナーや無効化ボタン、空状態などが自然に一貫して保たれます。

良い判定基準:多くの「要素を見つけてクラスを切り替える」ようなコードを書いているなら、そのモデルと戦っている可能性が高いです。UIが状態と乖離する場合、多くは状態の所有権の問題です。

React以前のDOM中心アーキテクチャの最大の問題点は?

React以前は、多くのアプリがページ中心で構築され、サーバー側テンプレート+jQueryやプラグインが混在していました。振る舞いはサーバービュー、HTML属性、JSの初期化コードに分散していました。

よくある問題点:

  • テンプレートやページ間でUIが重複する
  • マークアップが変わるとセレクタが壊れるなど脆弱なセレクタ
  • スピナーやエラーメッセージなどのUI状態が不整合になる

Reactはチームを再利用可能なコンポーネントと予測可能な更新へと導きました。

「宣言的レンダリング」とは何で、なぜ重要?

宣言的レンダリングとは、DOMをどう変えるか手順で書くのではなく、ある状態でUIがどうあるべきかを表現することです。

従来の手順的コード:

  • 要素を見つける → ノードを作る → テキストを設定 → 追加 → 後で削除

宣言的レンダリングではレンダリングの出力に条件を表現します(例:「ログインしていれば名前を表示、していなければサインインボタンを表示」)。Reactが実際のDOMを整合させます。

なぜJSXはReactのモデルの定着を助けたのか?

JSXは、UI構造をそれを制御するロジックの近くに書けるようにしたため普及を助けました(条件、フォーマット、ハンドラなど)。これによりテンプレートとロジックを行き来する必要が減ります。

JSXはHTMLそのものではなくJavaScript呼び出しを生成します。重要なのは、変化するものを一箇所にまとめるという組織上の利点で、UIと振る舞いを一緒に置くほうが管理しやすい場合が多いという点です。

Reactのリコンシリエーションとは?`key`の実務的な意味は?

リコンシリエーションは、前回のレンダー結果と今回のレンダー結果を比較し、最小限のDOM差分を適用するプロセスです。

ポイントは「仮想DOMが魔法のパフォーマンストリックだ」という誤解ではなく、予測可能なモデルを提供する点です:

  • レンダーはまるでUIを最初から組み直すように書ける
  • Reactは差分だけを実際のDOMに適用してコストを抑える

リストをレンダリングするとき、Reactは古いアイテムと新しいアイテムを一致させる安定した方法が必要です。それが key の役割です。

{todos.map(todo => (
  <TodoItem key={todo.id} todo={todo} />
))}

key は安定かつ一意な値(IDなど)を使い、要素が並べ替え・挿入・削除されうる場合は配列のインデックスを避けてください。でないとコンポーネントインスタンスが誤って再利用され、入力値が入れ替わるなどの意外な挙動が起きます。

一方向データフローはReactアプリの設計をどう単純化する?

一方向データフローは、データが親から子へと渡され、子はコールバックで変更を要求するという考え方です。

例: 親が状態を所有し、それを子に props として渡す。子は onIncrement のようなコールバックを呼んで変更を要求します。

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>
  );
}

このパターンは「このデータの所有者は誰か?」を明確にし、デバッグ時に「状態がどこにあるか」を追えばよく、隠れた変更の迷路を追いかける必要が減ります。

実際のReactプロジェクトでの「継承よりコンポジション」とは?

継承ではなくコンポジションという考え方は、UIを小さくフォーカスされた部品から組み立てることを奨励します。多様なバリエーションのために基底クラスを深く継承するより、コンポーネントを組み合わせる方が柔軟で理解しやすいです。

日常的なパターン:

  • PageShell(ヘッダ/サイドバー/フッタ)
  • Stack / Grid(余白・配置)
  • Card(一貫したフレーミング)

これらは children を受け取り、ページ側が中身を決めます。children で足りないときは titlefooterrenderRow のようなスロット的なpropsを使います。

Hooks(カスタムフック)はUIを共有せずロジックを共有する方法として有効で、useDebouncedValueusePermissions のような再利用の手段を提供します。

アプリが成長するにつれて状態管理にどう対処すべき?

Reactではまずローカルなコンポーネント状態から始めるのが自然です(フォーム入力、ドロップダウンの開閉、ローディングなど)。しかしアプリが大きくなると、状態を複数の遠いコンポーネントで共有する必要が出てきます。

一般的な進め方:

  1. ローカル状態をデフォルトにする(特定コンポーネント固有のUI)
  2. 兄弟コンポーネント間の同期が必要なら状態をリフトアップする
  3. クロスカッティングな懸念(テーマ、ロケール、現在ユーザー)にはContextを使う
  4. 遠く離れた多数のコンポーネントが同じデータに依存する場合は外部ストア(Redux類似)を検討する

選択は流行ではなく、アプリの複雑さやチームのニーズで決めるべきです。

Related posts