Skip to main content

第19章:最初の「型定義」!

これから作るReactコンポーネントに「どんなデータが来るのか?」を、TypeScriptで先に宣言しておく回だよ。 型=設計図。設計図があると、VS Codeがずっと味方してくれる😊🛡️


ゴール 🎯

  • Propsの型をtypeでサクッと宣言できる
  • 必須/任意(オプショナル)の書き分けができる
  • 関数やchildrenを含むPropsも怖くない

まずは全体像 🗺️


ハンズオン①:自己紹介カードのProps型を作る 💳

1) ファイル作成

src/components/ProfileCard.tsx を新規作成。

2) Propsの型を宣言(まずは type でOK!)

// src/components/ProfileCard.tsx
export type ProfileCardProps = {
name: string; // 必須
age?: number; // 任意(?が付くと無くてもOK)
university: string;
avatarUrl?: string;
onFollow?: (userName: string) => void; // 関数を渡す場合
children?: React.ReactNode; // 中身を差し込めるように
};

3) コンポーネントに型を当てる

export default function ProfileCard({
name,
age,
university,
avatarUrl = "https://placehold.co/80x80", // デフォルト値もOK
onFollow,
children,
}: ProfileCardProps) {
return (
<article
style={{
display: "grid",
gridTemplateColumns: "80px 1fr",
gap: 12,
padding: 16,
border: "1px solid #ddd",
borderRadius: 12,
}}
>
<img
src={avatarUrl}
alt={`${name}のアイコン`}
width={80}
height={80}
style={{ borderRadius: "50%" }}
/>
<div>
<h3 style={{ margin: "4px 0" }}>{name} 🎀</h3>
<p style={{ margin: 0 }}>
{university} {age != null ? `/ ${age}` : ""}
</p>

{/* 子要素を差し込める */}
<div style={{ marginTop: 8 }}>{children}</div>

{/* 関数Propsがある時だけボタンを表示 */}
{onFollow && (
<button
style={{ marginTop: 8, padding: "6px 10px", borderRadius: 8 }}
onClick={() => onFollow(name)}
>
フォローする✨
</button>
)}
</div>
</article>
);
}

4) 使ってみる(App.tsx)

// src/App.tsx
import ProfileCard from "./components/ProfileCard";

export default function App() {
return (
<main style={{ padding: 20, display: "grid", gap: 16 }}>
<ProfileCard
name="Rin"
university="Sakura Univ."
onFollow={(user) => alert(`${user} をフォローしたよ💘`)}
>
<span>好き:ラテ・猫・React 🐱☕</span>
</ProfileCard>

<ProfileCard
name="Mina"
university="Maple Univ."
age={21}
avatarUrl="https://placehold.co/80x80?text=M"
>
<em>ポートフォリオ準備中…🔥</em>
</ProfileCard>
</main>
);
}

Props型づくりのコツ 🌟

1) 必須と任意の見極め

  • 必須:無いとUIが成立しない → そのまま
  • 任意:無くても成立 → ? を付ける + できればデフォルト値を用意
type ButtonProps = {
label: string; // 必須
variant?: "primary"|"ghost"; // 任意(ユニオン型で選択肢を制限)
};

2) 関数Propsは「何を受け取り、何を返すか」を明確に

type OnSelect = (id: string) => void;
type ItemProps = { onSelect?: OnSelect; };

3) childrenReact.ReactNode

type CardProps = { children?: React.ReactNode };

4) ユニオン型で安全・快適 🎛️

type Tone = "info" | "success" | "warn" | "error";
type BadgeProps = { tone?: Tone };

“型が守ってくれた!”を体験してみよう 🛡️

NG例:渡す値がユニオンに無い

// ❌ 型エラー: "danger" は Tone に含まれない
// <Badge tone="danger" />

NG例:必須を渡し忘れ

// ❌ 型エラー: label は必須
// <Button />

VS Codeが赤線で即気づける。直すのも一瞬!⏱️✨


ハンズオン②:既存データからProps型を“流用”する(応用ミニ)🧠

アプリには「データ型」と「表示用のProps型」が出てくることが多いよ。 全部そのまま渡す必要はなくて、表示に必要な一部だけ型で抜き出すのがコツ!

// 例:APIから来るユーザーデータ
type User = {
id: string;
name: string;
email: string;
avatarUrl?: string;
university?: string;
createdAt: string;
};

// 表示に必要なぶんだけ Pick で抽出
type UserChipProps = Pick<User, "name" | "avatarUrl"> & {
onClick?: () => void;
};

export function UserChip({ name, avatarUrl, onClick }: UserChipProps) {
return (
<button onClick={onClick} style={{ display: "flex", gap: 8, alignItems: "center" }}>
<img
src={avatarUrl ?? "https://placehold.co/24x24"}
width={24}
height={24}
style={{ borderRadius: "50%" }}
alt=""
/>
<span>{name}</span>
</button>
);
}

Pick / Omit実務で超つよい。必要なカラムだけPropsにできて、型の重複も減るよ💡


よくあるつまずき 🆘


チャレンジ課題 💪🎮

  1. ProductCard を作ってみよう

    • 必須: title: string, price: number
    • 任意: thumbnailUrl?: string, onAddToCart?: (title: string) => void
    • children にバッジ類を差し込めるように
  2. price を小数点1ケタで表示してみる(例: 1290 → ¥1,290.0

  3. variant"solid" | "outline" のユニオン型で追加して、見た目を少し変えてみる


まとめ 🌸

  • Propsの型=設計図まずは type で宣言していけばOK!
  • 必須/任意/関数/childrenを押さえれば、ほぼ全部いける✨
  • Pick / Omit などでデータ型→表示用に最小化するのがプロっぽい🌟

次回は 第20章!実際に「自己紹介カード部品」を型付きで作り切って、型のありがたみを全身で感じよ〜〜!🙌💙