第6回

propsでデータを渡す:FeatureCardコンポーネントを作る

Reactのpropsの仕組みをTypeScriptの型定義と合わせて解説。コピペから脱してデータを外から受け取るコンポーネントの作り方を学ぶ。

·18分で読める
たける
たける トップページの「特徴紹介」セクション、カードが3枚あるんですけど、同じHTML構造を3回コピペしてしまいました。
りこ
りこ 見せて。
// たけるが書いたコード
function Features() {
  return (
    <div>
      <div className="feature-card">
        <span>📖</span>
        <h3>対話形式で学べる</h3>
        <p>登場人物との会話を通じてReactの概念を自然に習得できます。</p>
      </div>
      <div className="feature-card">
        <span>🛠</span>
        <h3>実際に動くものを作る</h3>
        <p>概念だけでなく、実際にWebサイトを完成させながら学びます。</p>
      </div>
      <div className="feature-card">
        <span>🚀</span>
        <h3>公開まで完走する</h3>
        <p>Cloudflare Pagesへのデプロイまで、一連の流れを体験します。</p>
      </div>
    </div>
  )
}
りこ
りこ この3枚、構造は全部同じで中身だけ違う。これがpropsを使うタイミング。

propsとは

props(プロップス)は、コンポーネントに外から渡すデータのこと。関数の引数と同じ考え方で、「形は同じでも、渡す値によって表示が変わる」コンポーネントを作れる。

// 「形」を1回だけ定義する
function FeatureCard({ icon, title, description }) {
  return (
    <div className="feature-card">
      <span>{icon}</span>
      <h3>{title}</h3>
      <p>{description}</p>
    </div>
  )
}

// 3枚とも同じコンポーネントで、データだけ変える
function Features() {
  return (
    <div>
      <FeatureCard icon="📖" title="対話形式で学べる" description="登場人物との会話を通じて..." />
      <FeatureCard icon="🛠" title="実際に動くものを作る" description="概念だけでなく..." />
      <FeatureCard icon="🚀" title="公開まで完走する" description="Cloudflare Pagesへの..." />
    </div>
  )
}
たける
たける `{ icon, title, description }` って、引数の中に直接書いてるんですね。オブジェクトの分割代入ですか?
りこ
りこ そう。Reactはpropsをオブジェクトで受け取る。`props.icon`・`props.title` と書いてもいいけど、引数の時点で分割代入するのが定番。

TypeScriptで型を定義する

TypeScriptを使うと、propsに「どんなデータを渡すべきか」を明示できる。型が合わないときはコードを書いた瞬間にエラーが出る。

// src/components/FeatureCard.tsx

// ① propsの型を定義する
type FeatureCardProps = {
  icon: string
  title: string
  description: string
}

// ② 引数に型を書く
export const FeatureCard = ({ icon, title, description }: FeatureCardProps) => {
  return (
    <div className="feature-card">
      <span>{icon}</span>
      <h3>{title}</h3>
      <p>{description}</p>
    </div>
  )
}
たける
たける 型をつけると何がうれしいんですか? 書くのが面倒な気もして。
りこ
りこ 試してみて。`description` を渡し忘れて `FeatureCard` を使ってみて。
たける
たける すぐ赤波線が出た。「Property 'description' is missing」って。ブラウザで確認する前に気づけますね。
りこ
りこ それが型の価値。バグが実行前に見つかる。

オプショナルなprops

すべてのpropsが必須とは限らない。省略できるpropsには ? をつける。

type FeatureCardProps = {
  icon: string
  title: string
  description: string
  link?: string   // ? をつけると省略可能
}

export const FeatureCard = ({ icon, title, description, link }: FeatureCardProps) => {
  return (
    <div className="feature-card">
      <span>{icon}</span>
      <h3>{title}</h3>
      <p>{description}</p>
      {link && <a href={link}>詳しく見る</a>}  {/* linkがある場合だけ表示 */}
    </div>
  )
}

完成形:データを配列で管理する

propsが機能したら、データを配列にまとめると後から管理しやすい。

// src/components/Features.tsx
import { FeatureCard } from './FeatureCard'

const features = [
  {
    id: 'dialogue',
    icon: '📖',
    title: '対話形式で学べる',
    description: '登場人物との会話を通じてReactの概念を自然に習得できます。',
  },
  {
    id: 'hands-on',
    icon: '🛠',
    title: '実際に動くものを作る',
    description: '概念だけでなく、実際にWebサイトを完成させながら学びます。',
  },
  {
    id: 'deploy',
    icon: '🚀',
    title: '公開まで完走する',
    description: 'Cloudflare Pagesへのデプロイまで、一連の流れを体験します。',
  },
]

export const Features = () => {
  return (
    <section>
      <h2>このシリーズの特徴</h2>
      <div className="features-grid">
        {features.map(f => (
          <FeatureCard key={f.id} {...f} />
        ))}
      </div>
    </section>
  )
}
たける
たける `{...f}` って何ですか? スプレッド演算子ですか?
りこ
りこ そう。オブジェクトのプロパティを全部propsとして展開する書き方。`icon={f.icon} title={f.title} description={f.description}` と1つずつ書くのと同じ意味。データの形とpropsの型が一致しているときに使える省略形。
たける
たける 整理すると──propsは「コンポーネントへの引数」で、配列のデータとmapを組み合わせると「データが増えても同じコンポーネントで対応できる」ってことですね。
りこ
りこ 完璧。それがReactのコンポーネント思考の核心。
📁 第6回完了時点のファイル構成・完成コード

フォルダ構成

src/
├── components/
│   ├── Header.tsx
│   ├── Footer.tsx
│   └── FeatureCard.tsx   ← 今回追加
└── App.tsx               ← FeatureCardを使うように更新

FeatureCard.tsx

type FeatureCardProps = {
  icon: string
  title: string
  description: string
}

export const FeatureCard = ({ icon, title, description }: FeatureCardProps) => {
  return (
    <div className="bg-white rounded-2xl border border-slate-100 shadow-sm p-6">
      <span className="text-3xl mb-4 block">{icon}</span>
      <h3 className="text-lg font-bold text-slate-900 mb-2">{title}</h3>
      <p className="text-slate-600 text-sm leading-7">{description}</p>
    </div>
  )
}

App.tsx(FeatureCardを使う部分)

import { Header } from './components/Header'
import { Footer } from './components/Footer'
import { FeatureCard } from './components/FeatureCard'

function App() {
  return (
    <div className="min-h-screen flex flex-col">
      <Header />
      <main className="flex-1">
        <section className="max-w-5xl mx-auto px-4 sm:px-8 py-16">
          <h2 className="text-2xl font-bold text-slate-900 mb-8 text-center">
            このサイトで学べること
          </h2>
          <div className="grid grid-cols-1 md:grid-cols-3 gap-6">
            <FeatureCard
              icon="⚛️"
              title="Reactの基礎"
              description="コンポーネント・props・stateの概念を実例で理解する。"
            />
            <FeatureCard
              icon="🎨"
              title="デザインシステム"
              description="Tailwind CSSでUIを素早く構築する方法を学ぶ。"
            />
            <FeatureCard
              icon="🚀"
              title="デプロイまで"
              description="Cloudflare Pagesに公開して、URLを手に入れる。"
            />
          </div>
        </section>
      </main>
      <Footer />
    </div>
  )
}

export default App

まとめ

  • props:コンポーネントに外から渡すデータ。関数の引数と同じ考え方
  • 引数で分割代入するのが定番:({ icon, title }: Props)
  • TypeScriptで型定義すると、渡し忘れや型ミスをコードを書いた時点で検出できる
  • オプショナルなpropsには ? をつける
  • {...object} でオブジェクトのプロパティを一括でpropsに展開できる

次の第7回では、Tailwind CSSを使ってトップページのデザインを整える。