Next.jsによるWebアプリケーション開発概論

コンポーネント分割と再利用設計

6Next.js基礎:ReactコンポーネントとJSX・Propsの基本
Next.jsTailwind CSSアプリ開発開発Web開発

この節で学ぶこと

この節では、コンポーネントをどこで分けるべきか、なぜ再利用を意識すると強い設計になるのか、そして Next.js の中でどう整理すると見通しがよくなるのか を学びます。

React 公式は、UI をコンポーネントという部品に分けて考え、親子関係のある ツリー構造 として捉えることが重要だと説明しています。

さらに “Thinking in React” では、まず UI をコンポーネント階層に分解することを出発点にしています。

この節の到達目標は4つです。

  1. コンポーネント分割の目的が「細かく切ること」ではなく、責務を分けて読みやすくすること だと説明できることです。
  2. どこまでを1つの部品にするかを、見た目・役割・再利用性の観点から判断できることです。
  3. props と children を使って再利用しやすい UI 部品を作る基本感覚を持つことです。
  4. Next.js の App Router ではファイルやフォルダの整理も含めて、コンポーネント設計が画面設計と直結していると理解することです。

Next.js の公式は、app ディレクトリの構造や Route Groups を使って、論理的にプロジェクトを整理できると説明しています。

コンポーネント分割とは何か

1枚の画面を部品の集まりとして考える

React 公式では、React アプリはコンポーネントでできており、コンポーネントは小さなボタンにも大きなページにもなり得ると説明されています。さらに、コンポーネントはネストして使えます。

たとえば、商品一覧ページを考えてみます。

画面全体を1つの巨大な page.tsx に書くこともできますが、実際には次のようなまとまりに見えるはずです。

  • ページ全体
  • ヘッダー
  • 商品一覧
  • 商品カード
  • 購入ボタン

このように分けて考えるのが、コンポーネント分割の入口です。

React 公式の “Thinking in React” でも、まずデザインを見てコンポーネント階層へ分解することから始めます。

分ける目的は再利用と保守性

コンポーネント分割の目的は、単にファイルを増やすことではありません。

本質は、同じ形を何度も使えるようにすること と 修正箇所を狭くすること です。

React 公式は、コンポーネントを使うと既に書いた UI 要素を再利用でき、開発を速くできると説明しています。

たとえば、商品カードの背景色を変えたいとき、カードが10か所にコピペされていたら10か所直す必要があります。

しかし ProductCard という部品にしてあれば、1か所の修正で済みます。ここが、分割と再利用の大きな価値です。

どこで分割するのか

見た目のまとまりで分ける

最初の判断基準として分かりやすいのは、画面上で意味のあるまとまりになっているか です。

React 公式の考え方でも、UI をツリーとして捉えると、見た目のまとまりが親子関係に対応しやすくなります。

たとえば次のような単位は、コンポーネントにしやすいです。

  • ヘッダー
  • フッター
  • サイドバー
  • プロフィールカード
  • 商品カード
  • フォーム入力欄 逆に、単なる1行の <span> を機械的に全部コンポーネント化すると、かえって読みにくくなることがあります。

分割は「できるか」ではなく、「分ける意味があるか」で決める方が自然です。

役割で分ける

見た目だけでなく、責務 でも分けられます。

React 公式の “Thinking in React” は、UI を部品へ分けたうえで、どのコンポーネントがどんな役割を持つかを整理していく流れです。

たとえば、ブログ記事一覧ページならこうです。

  • ArticleList は一覧全体を並べる役割
  • ArticleCard は1件分の記事を表示する役割
  • ArticleMeta は投稿日や著者名を見せる役割

このように、何を担当する部品かが名前で言えるなら、分割候補としてかなり良いです。

再利用できるかで分ける

コンポーネントは、別の場所でも同じ形で使いたいか という観点でも分けられます。React 公式の props の説明では、親から子へデータを渡すことで、同じコンポーネントを中身だけ変えて再利用できると示されています。

たとえば、次のような UI は再利用しやすいです。

  • ボタン
  • カード
  • セクション見出し
  • モーダル枠
  • タグ表示

このような部品は、props を使って内容を差し替えると非常に強いです。

分割しない方がよい場面

小さすぎて意味が薄いとき

コンポーネントは分ければ分けるほど良い、というものではありません。

React 公式はコンポーネントを組み立てて UI を作る考え方を示していますが、同時に 部品として意味のある単位 で考えることが前提です。

たとえば、次のような分割は少しやりすぎかもしれません。

function TitleText() {
  return <span>商品名</span>
}

この TitleText が他でも使われず、見た目や役割の差もないなら、単に ProductCard の中へ書いた方が読みやすいことがあります。

逆に大きすぎるとき

1つのコンポーネントが長くなりすぎるのも問題です。

React 公式の “Thinking in React” がまず UI を階層へ分解させるのは、巨大コンポーネントが読みづらく、役割が混ざりやすいからです。

次のような状態は、分割を考えた方がよいサインです。

  • 1ファイルが何百行にもなっている
  • 同じ JSX のかたまりが何回も出てくる
  • 「この部分だけ直したい」がやりにくい
  • props が多すぎて役割が曖昧になっている

再利用しやすい設計の基本

Props で中身を変える

再利用しやすいコンポーネントを作る基本は、固定の内容を中へ埋め込まず、外から props で渡せるようにすること です。React 公式では、親から子へ props を渡して UI を調整する流れを基礎として説明しています。

まずは固定のカードです。

type ProductCardProps = {
  title: string
  price: number
}

function ProductCard({ title, price }: ProductCardProps) {
  return (
    <article>
      <h2>{title}</h2>
      <p>{price}円</p>
    </article>
  )
}

使う側はこうです。

export default function Page() {
  return (
    <main>
      <ProductCard title="本革バッグ" price={19800} />
      <ProductCard title="レザー財布" price={9800} />
    </main>
  )
}

この形なら、ProductCard の見た目は1つ、内容だけ差し替えられます。

これが再利用の基本です。

children で中に差し込めるようにする

React では、タグの中にネストした内容は children として受け取れます。React 公式は、children を「穴」のように使い、ラッパー系コンポーネントへ任意の JSX を差し込めると説明しています。

import type { ReactNode } from 'react'

type CardProps = {
  children: ReactNode
}

function Card({ children }: CardProps) {
  return <div className="card">{children}</div>
}

使う側はこうです。

export default function Page() {
  return (
    <Card>
      <h2>お知らせ</h2>
      <p>営業時間が変更になりました。</p>
    </Card>
  )
}

この設計のよいところは、枠だけを共通化し、中身は自由に差し替えられる ことです。

モーダル、カード、セクション枠、レイアウト枠などで特に強いです。

見た目の責務とデータの責務を分ける

React 公式の “Sharing State Between Components” では、状態を最も近い共通親へ持ち上げ、そこから props で下ろす考え方を説明しています。これは、どこがデータを持ち、どこが表示に専念するかを分ける発想です。

たとえば、

  • ProductList が配列データを持つ
  • ProductCard は受け取った1件を表示するだけ

という分担にすると、役割がきれいに分かれます。

type Product = {
  id: number
  title: string
  price: number
}

type ProductCardProps = {
  product: Product
}

function ProductCard({ product }: ProductCardProps) {
  return (
    <article>
      <h2>{product.title}</h2>
      <p>{product.price}円</p>
    </article>
  )
}

export default function ProductList() {
  const products: Product[] = [
    { id: 1, title: '本革バッグ', price: 19800 },
    { id: 2, title: 'レザー財布', price: 9800 },
  ]

  return (
    <main>
      {products.map((product) => (
        <ProductCard key={product.id} product={product} />
      ))}
    </main>
  )
}

この形だと ProductCard は「表示専用部品」に近づき、再利用しやすくなります。

コンポーネント階層で考える

UI はツリー構造になる

React 公式は、UI を tree として捉えることが有用だと説明しています。親と子、兄弟関係を意識すると、どこへ props を渡すのか、どこへ state を置くのかが見えやすくなります。

たとえば、商品一覧ページなら次のようなツリーになります。

Page
├─ Header
├─ ItemList
│  ├─ ItemCard
│  ├─ ItemCard
│  └─ ItemCard
└─ Footer

この見方ができると、

  • 「どの部品が一覧を担当するのか」
  • 「どの部品が1件を担当するのか」
  • 「どこまで共通化するべきか」

がかなり整理しやすくなります。

まずデザインを見て四角で囲む

React 公式の “Thinking in React” では、デザインモックを見ながら、コンポーネントになりそうなまとまりを囲んでいく方法が紹介されています。

画面を見て、次のように問いかけます。

  • この部分は独立した役割を持っているか
  • 他の画面でも再利用しそうか
  • props を変えれば使い回せそうか
  • ファイルを分けた方が読みやすいか

この4つに複数当てはまるなら、コンポーネント候補になりえます。

Next.js での整理方法

app 配下のページと部品を分ける

Next.js の公式は、app ディレクトリを App Router の中心として説明しており、プロジェクト構造の整理方法も案内しています。

初心者向けには、たとえば次のような分け方が分かりやすいです。

src/
  app/
    page.tsx
    products/
      page.tsx
  components/
    ProductCard.tsx
    PageHeader.tsx
    Card.tsx

この形だと、

  • app はルートごとの入口
  • components は再利用部品

という役割分担が見えやすくなります。

Route Groups は整理に使える

Next.js の Route Groups は、URL に影響させずにフォルダを論理グループとして整理する仕組みです。公式でも、project files を logical groups に整理する用途があると説明されています。

たとえば、将来的にはこんな整理もできます。

app/
  (shop)/
    products/
      page.tsx
    cart/
      page.tsx
  (marketing)/
    about/
      page.tsx

これは少し先の話ですが、画面設計とファイル整理がつながっている のが Next.js の面白いところです。

再利用設計で気をつけたいこと

props を増やしすぎない

再利用を意識しすぎると、何でも1つの万能コンポーネントへ押し込みたくなることがあります。

しかし props が多すぎると、逆に使いづらくなります。React 公式は props によるデータ受け渡しを基本としつつ、情報が深くなりすぎる場合には Context も選択肢になると説明しています。

たとえば、Button に次のような props が大量に付くなら要注意です。

type ButtonProps = {
  label: string
  size: 'sm' | 'md' | 'lg'
  variant: 'primary' | 'secondary'
  isLoading: boolean
  iconLeft?: string
  iconRight?: string
  fullWidth?: boolean
  rounded?: boolean
  shadow?: boolean
}

もちろん必要なこともあります。

ただ、「1つで全部を解決しようとしていないか」は立ち止まって考える価値があります。

似ているが別物を無理に共通化しない

共通化は気持ちがよい反面、早すぎると失敗しやすいです。

React の思想自体は部品化と再利用ですが、何でも1つにまとめることを推奨しているわけではありません。UI と責務のまとまりに応じて分けるのが基本です。

たとえば、商品カードとブログ記事カードは見た目が似ていても、役割やデータが大きく違うなら、最初は別コンポーネントの方が安全です。

props のバケツリレーが深くなったら見直す

React 公式は、props を深い階層まで何段も渡すのが冗長になったら Context が役立つと説明しています。

つまり、

  • Page
  • Section
  • Wrapper
  • List
  • Card と何段も経由して、同じ値をずっと渡しているなら、設計を少し見直す合図かもしれません。

この段階では「props は親から子へ渡す」「深すぎるとつらくなることがある」くらいの理解で十分です。

実践的な分割例

分割前

まずは1つのファイルに全部書いた形です。

export default function Page() {
  return (
    <main>
      <header>
        <h1>商品一覧</h1>
      </header>

      <section>
        <article>
          <h2>本革バッグ</h2>
          <p>19800円</p>
          <button>購入する</button>
        </article>

        <article>
          <h2>レザー財布</h2>
          <p>9800円</p>
          <button>購入する</button>
        </article>
      </section>
    </main>
  )
}

まだ小さいので読めます。

ただ、商品が増えるとすぐつらくなります。

分割後

type Product = {
  id: number
  title: string
  price: number
}

type ProductCardProps = {
  product: Product
}

function ProductCard({ product }: ProductCardProps) {
  return (
    <article>
      <h2>{product.title}</h2>
      <p>{product.price}円</p>
      <button>購入する</button>
    </article>
  )
}

function PageHeader() {
  return (
    <header>
      <h1>商品一覧</h1>
    </header>
  )
}

export default function Page() {
  const products: Product[] = [
    { id: 1, title: '本革バッグ', price: 19800 },
    { id: 2, title: 'レザー財布', price: 9800 },
  ]

  return (
    <main>
      <PageHeader />
      <section>
        {products.map((product) => (
          <ProductCard key={product.id} product={product} />
        ))}
      </section>
    </main>
  )
}

この形になると、

  • PageHeader は見出し担当
  • ProductCard は1件表示担当
  • Page は一覧全体担当 と責務が分かれます。

この「誰が何を担当しているか」が見えることが、分割設計の大きな価値です。

ミニ練習問題

問1

コンポーネント分割の主な目的として最も適切なものはどれですか。

A. ファイル数を増やすこと

B. 責務を分け、再利用しやすくすること

C. JSX を使わなくすること

D. CSS をなくすこと

答え

B

解説

React 公式は、UI をコンポーネントへ分けて考え、再利用やデータフローを整理する考え方を示しています。分割の目的は、読みやすさと再利用性、保守性の向上です。

問2

次のうち、コンポーネントに分ける判断基準として適切なものはどれですか。

A. 見た目や役割のまとまりがあるか

B. 文字数が3文字以上か

C. 必ず1タグごとに分けられるか

D. HTML タグかどうか

答え

A

解説

“Thinking in React” では、まず UI をコンポーネント階層へ分解することから始めます。見た目、役割、データの流れがまとまっているかが判断材料になります。

問3

children を使う設計の説明として正しいものはどれですか。

A. 固定の文字列しか渡せない

B. ラッパー系コンポーネントの中へ任意の JSX を差し込める

C. 画像 URL しか渡せない

D. Next.js では使えない

答え

B

解説

React では、ネストした JSX は children props として受け取れます。これはカード枠やモーダル枠のようなラッパー部品で特に有効です。

問4

複数コンポーネントで同じ state を共有したいとき、React 公式が基本として説明している考え方はどれですか。

A. state を各子に複製する

B. state を最も近い共通親へ持ち上げる

C. すべて localStorage に入れる

D. 必ず Context を最初から使う

答え

B

解説

React 公式の “Sharing State Between Components” では、共有したい state は最も近い共通親へ持ち上げ、そこから props として渡すと説明しています。

問5

Next.js の Route Groups の説明として正しいものはどれですか。

A. URL を必ず長くするための機能

B. URL に影響を与えず、ファイルを論理グループで整理できる機能

C. CSS Modules の別名

D. public フォルダの代わりになる機能

答え

B

解説

Next.js 公式は、Route Groups によって route segments や project files を URL に影響させず論理グループで整理できると説明しています。

まとめ

コンポーネント分割と再利用設計の本質は、画面を意味のある部品に分け、その部品を必要に応じて繰り返し使えるようにすること です。React 公式は、UI をコンポーネントのツリーとして考え、まず階層へ分解することを基礎に置いています。props や children を使えば、中身を差し替えながら同じ部品を再利用できます。

この節で特に持ち帰ってほしいのは、次の5点です。

  • 分割の目的は責務分離と再利用である。
  • 見た目・役割・再利用性が分割の判断材料になる。
  • children はラッパー系コンポーネントの再利用性を高める。
  • state を共有したいときは、まず共通親へ持ち上げる考え方が基本である。
  • Next.js ではファイル構造の整理も、コンポーネント設計とつながっている。

ここが腹落ちすると、コンポーネントは「とりあえず分ける箱」ではなくなります。

役割のある部品をどう組み合わせるかを考える設計の道具 として見えてきます。次の節でイベント処理へ進むと、再利用できる部品に「振る舞い」が加わり、さらに実践的になっていきます。

参考文献

  • React Docs, Thinking in React
  • React Docs, Your First Component
  • React Docs, Understanding Your UI as a Tree
  • React Docs, Sharing State Between Components
  • React Docs, Passing Data Deeply with Context
  • Next.js Docs, Getting Started: Project Structure
  • Next.js Docs, App Router: Getting Started
  • Next.js Docs, Route Groups
  • React TypeScript Cheatsheet, Function Components
  • React TypeScript Cheatsheet, ReactNode
教材トップへ戻る