TypeScript レッスン3
インターフェース
オブジェクトの型を定義して、構造を明確にしよう
interfaceの基本
interfaceは オブジェクトの「形」を定義する仕組みです。 どんなプロパティがあり、それぞれがどんな型かを記述します。 TypeScriptでは、オブジェクトの構造が一致すれば型チェックを通過します(構造的部分型)。
// interface でオブジェクトの型を定義
interface User {
id: number;
name: string;
email: string;
}
// この型に合うオブジェクトを作成
const user: User = {
id: 1,
name: "太郎",
email: "taro@example.com",
};
// プロパティが足りないとエラー
// const bad: User = { id: 1, name: "太郎" };
// Error: Property 'email' is missing
// 余分なプロパティもエラー(直接代入時)
// const bad2: User = { id: 1, name: "太郎", email: "...", age: 25 };
// Error: Object literal may only specify known propertiesオプショナルプロパティとreadonly
プロパティを任意にしたり、変更不可にしたりできます。
interface BlogPost {
readonly id: number; // 変更不可
title: string;
body: string;
tags?: string[]; // オプショナル(あってもなくてもOK)
publishedAt?: Date; // オプショナル
readonly createdAt: Date; // 変更不可
}
const post: BlogPost = {
id: 1,
title: "TypeScript入門",
body: "TypeScriptは素晴らしい...",
createdAt: new Date(),
// tags と publishedAt は省略OK
};
// readonlyプロパティは変更できない
// post.id = 2; // Error: Cannot assign to 'id' because it is a read-only property
// post.createdAt = new Date(); // Error
// 通常のプロパティは変更OK
post.title = "TypeScript完全入門";
// オプショナルプロパティの安全なアクセス
const tagCount = post.tags?.length ?? 0; // 0
const firstTag = post.tags?.[0] ?? "なし"; // "なし"メソッドとインデックスシグネチャ
interfaceにはメソッドの型定義や、動的なキーの型定義も含められます。
// メソッドを持つinterface
interface Calculator {
value: number;
add(n: number): Calculator; // メソッド構文
subtract: (n: number) => Calculator; // プロパティ構文
reset(): Calculator;
getResult(): number;
}
// インデックスシグネチャ: 任意のキーを許容
interface StringMap {
[key: string]: string; // 任意の文字列キーで string 値
}
const translations: StringMap = {
hello: "こんにちは",
goodbye: "さようなら",
thanks: "ありがとう",
// 何個でも追加OK
};
// 固定プロパティ + インデックスシグネチャ
interface Config {
version: number; // 必須
debug: boolean; // 必須
[key: string]: unknown; // その他は何でもOK
}
const config: Config = {
version: 1,
debug: true,
apiUrl: "https://api.example.com",
timeout: 3000,
};interfaceの拡張(extends)
extendsを使って、 既存のinterfaceを拡張(継承)できます。コードの再利用性が高まります。
// 基本のinterface
interface Animal {
name: string;
age: number;
}
// 拡張: Animal のプロパティを継承 + 追加
interface Dog extends Animal {
breed: string;
isGoodBoy: boolean;
}
// Dog は name, age, breed, isGoodBoy を持つ
const shiba: Dog = {
name: "ポチ",
age: 3,
breed: "柴犬",
isGoodBoy: true,
};
// 複数のinterfaceを同時に拡張
interface Timestamps {
createdAt: Date;
updatedAt: Date;
}
interface SoftDelete {
deletedAt?: Date;
isDeleted: boolean;
}
// 複数の interface を extends で合成
interface Article extends Timestamps, SoftDelete {
id: number;
title: string;
body: string;
author: string;
}
// Article は全プロパティを持つ
const article: Article = {
id: 1,
title: "TypeScript入門",
body: "...",
author: "太郎",
createdAt: new Date(),
updatedAt: new Date(),
isDeleted: false,
};
// interface の宣言マージ(同名で追加定義)
interface Window {
myCustomProp: string;
}
// 既存の Window interface に myCustomProp が追加されるオブジェクトの構造を実践する
interfaceの考え方をJavaScriptで実践してみましょう。 オブジェクトの構造をバリデーションする関数を作ります。
Monaco + Shiki / Tab でインデント / Ctrl(Cmd)+Enter で再実行
index.htmllazy
1 lines0 issues
Monaco Editor を準備しています
表示領域に入った時点で Monaco と Shiki を初期化します。
preview.local
Live PreviewConsole
console.log / warn / error の出力がここに表示されます。
type vs interface
typeとinterfaceは 似ていますが、いくつかの違いがあります。
// === interface でできて type でできないこと ===
// 宣言マージ(同名で追加)
interface User {
name: string;
}
interface User {
age: number;
}
// User は { name: string; age: number; } になる
// type では同名の再宣言はエラー
// type Point = { x: number; };
// type Point = { y: number; }; // Error!
// === type でできて interface でできないこと ===
// プリミティブ型のエイリアス
type ID = string | number;
// ユニオン型
type Status = "active" | "inactive" | "pending";
// タプル型
type Pair = [string, number];
// マップ型
type Readonly<T> = { readonly [P in keyof T]: T[P] };
// === どちらを使うべき? ===
// オブジェクトの型定義 → interface が推奨
// - 拡張しやすい (extends)
// - エラーメッセージがわかりやすい
// - 宣言マージでライブラリの型を拡張できる
// ユニオン型、プリミティブ、関数型 → type を使う
// - interface では表現できない
// チームで統一することが大切!まとめ
interfaceでオブジェクトの構造を定義?でオプショナルプロパティ、readonlyで変更不可extendsで既存のinterfaceを拡張- インデックスシグネチャで動的なキーに対応
- 同名interfaceは自動マージされる
- オブジェクト型は interface、その他は type を使うのが一般的