第18章:TSのinterfaceとtype、どっち使う?
— まずは type でOK!でも違いも知っておこ〜🌸 —
きょうのゴール 🎯
interfaceとtypeの違いがサクッと分かる- ReactのProps定義で「まず何を使えばいいか」判断できる
- ありがちなエラー/つまずきを秒速回避できる
結論さきどり 🍮(TL;DR)
- 迷ったら
typeでPropsを定義してOK(ユニオンやタプル、テンプレ文字列型が使えて万能🙆♀️) interfaceは宣言マージ(あとから拡張)が必要な時や、公開APIの拡張ポイントにしたい時に✨
サクッとおさらい:どっちも「型の名前」を付ける🍀
interface:主にオブジェクトの形を表す。宣言マージできる(同名をあとから拡張可)type:なんでもに別名を付けられる(プリミティブ・ユニオン・タプル・関数型・テンプレ文字列型・条件型など)
まずは「選び方」を図で👀
ReactのPropsはこう書くのがラクだよ ✨
✅ 推し:type でProps
// Button.tsx
type ButtonProps = {
label: string;
variant?: "primary" | "ghost";
onClick?: () => void;
disabled?: boolean;
};
export default function Button({ label, variant = "primary", ...rest }: ButtonProps) {
return (
<button
data-variant={variant}
{...rest}
style={{ padding: "8px 12px", borderRadius: 8 }}
>
{label}
</button>
);
}
🧪 使ってみる
// App.tsx
import Button from "./Button";
export default function App() {
return (
<main style={{ padding: 16 }}>
<Button label="送信" onClick={() => alert("送ったよ📨")} />
<Button label="キャンセル" variant="ghost" disabled />
</main>
);
}
variantを"primary" | "ghost"みたいにユニオン型にしやすいのがtypeの強み💪
interface で「あとから拡張」もアリ 🧩
宣言マージの例(interface だけできる魔法🪄)
// theme.d.ts(型定義の置き場)
interface AppTheme {
primary: string;
spacing: number;
}
// 別ファイルで拡張(同名で追記OK)
interface AppTheme {
rounded: number;
}
type は同名再定義できないので、こういう「外部に拡張ポイントを開ける」設計には interface が便利💡
同じことをする2つの書き方(どっちでもOKな場面)😌
継承/合成
// interface の extends
interface Base {
id: string;
}
interface User extends Base {
name: string;
}
// type の交差型(&)
type BaseT = { id: string };
type UserT = BaseT & { name: string };
ReactのPropsでは**交差型(
&)**で合成する書き方がスッキリしがち✨
type でしかできない代表ワザ 🪄
// 文字列テンプレ型
type ButtonVariant = `tone-${"primary" | "secondary"}`; // "tone-primary" | "tone-secondary"
// タプル
type Point = [number, number];
// ユニオンの組み合わせ
type LoadingState = "idle" | "loading" | "success" | "error";
// 条件型(高度)
type IdOf<T> = T extends { id: infer U } ? U : never;
interface のおいしい使い所 🍯
- 宣言マージやモジュール拡張(ライブラリの型を足す)
- グローバルの型を書き足すとき(例:
Windowの拡張)
// global.d.ts
declare global {
interface Window {
__APP_VERSION__: string;
}
}
export {};
ありがちエラー&対処法 🔥
- Duplicate identifier(重複識別子)
→
typeは同名で増やせない。名前を変える orinterfaceに切り替え - Property does not exist
→ 合成(
&/extends)の片方にしかないプロパティを使ってない? - 型が合わない → ユニオンに値を足すか、Props名/型を正しくそろえる
ハンズオン:置き換えてみよう🧪✨
1) interface → type
// before
interface CardProps {
title: string;
tone?: "info" | "warn";
}
// after
type CardProps = {
title: string;
tone?: "info" | "warn";
};
2) 合成して再利用(ボタンの基礎 + バリアント)
type BaseButton = {
disabled?: boolean;
onClick?: () => void;
};
type VariantButton = {
variant?: "primary" | "ghost";
};
type ButtonProps = BaseButton & VariantButton;
ミニチートシート 📝
| やりたいこと | おすすめ |
|---|---|
| ReactのProps・ユニオン・タプル・テンプレ文字列型 | type |
| 公開APIで他ファイルから拡張させたい | interface |
| 既存ライブラリの型を拡張(宣言マージ) | interface |
| 複数の型を合成してPropsにしたい | type の &(or interface extends) |
ミニテスト(3問)🎓
- ボタンの
variantを"solid" | "outline"にしたい。typeとinterfaceどっちがカンタン? - ライブラリの
Theme型にroundedを後から足したい。どうする? typeを同じ名前で2回書いたら…どうなる?
こたえ
type(ユニオンを作りやすい)interfaceの宣言マージ(同名で追記)- 重複エラー。同名再定義は不可(
interfaceならマージ可能)
まとめ 🌟
- 迷ったら**
type**でProps!ユニオン/タプル/テンプレ文字列型が強い💪 - 宣言マージや拡張ポイントが必要なら**
interface** - 実務は“
typeメイン +interfaceピンポイント”が快適だよ🧘♀️
次は 第19章!Propsの最初の型定義を実戦で作って、VS Code に型で守ってもらう感覚をつかもうね〜🛡️💙