目次
TypeScriptの型システムは「バグを実行前に見つける」道具として広く使われるようになったが、Union型・Generic・型ガードを使いこなしている開発者はまだ少ない。本記事では型システムの核心を理解し、AIコード生成ツールとの相性も解説する。
なぜ型が重要か
// ❌ JavaScriptでの実行時エラー
function greet(user) {
return `Hello, ${user.name.toUpperCase()}!`;
}
greet(null); // TypeError: Cannot read property 'name' of null
// → 本番環境でクラッシュ
// ✅ TypeScriptで事前に検出
function greet(user: { name: string }): string {
return `Hello, ${user.name.toUpperCase()}!`;
}
greet(null); // コンパイルエラー: 'null' は '{ name: string }' に割り当てられません
型は「コードのドキュメント」であり「コンパイル時のテスト」でもある。
基本型
// プリミティブ型
const name: string = "Alice";
const age: number = 30;
const isActive: boolean = true;
const nothing: null = null;
const maybe: undefined = undefined;
// 配列
const scores: number[] = [1, 2, 3];
const tags: Array<string> = ["TypeScript", "AI"];
// オブジェクト型
type User = {
id: number;
name: string;
email?: string; // ? は省略可能(optional)
};
// 関数型
type Formatter = (value: number) => string;
const format: Formatter = (n) => `${n.toFixed(2)}円`;
Union型——複数の型を許容する
// Union型: AまたはBの型
type StringOrNumber = string | number;
function formatId(id: StringOrNumber): string {
if (typeof id === 'string') {
return id.toUpperCase();
}
return id.toString().padStart(6, '0');
}
// Literal Union: 特定の値のみ許容
type Status = 'pending' | 'active' | 'inactive' | 'deleted';
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
function setStatus(status: Status) {
// status は 4つの文字列のいずれかであることが保証される
}
// Discriminated Union: タグ付き共用体(パターンマッチに最適)
type Shape =
| { kind: 'circle'; radius: number }
| { kind: 'rectangle'; width: number; height: number }
| { kind: 'triangle'; base: number; height: number };
function area(shape: Shape): number {
switch (shape.kind) {
case 'circle':
return Math.PI * shape.radius ** 2;
case 'rectangle':
return shape.width * shape.height;
case 'triangle':
return (shape.base * shape.height) / 2;
// TypeScriptが全ケースを網羅しているか確認する
}
}
Intersection型——型を合成する
type Timestamps = {
createdAt: Date;
updatedAt: Date;
};
type Identifiable = {
id: string;
};
// & で型を合成
type Entity = Identifiable & Timestamps;
type User = Entity & {
name: string;
email: string;
};
// User は id, createdAt, updatedAt, name, email をすべて持つ
const user: User = {
id: "u_123",
name: "田中",
email: "tanaka@example.com",
createdAt: new Date(),
updatedAt: new Date(),
};
Generic——型を引数にする
Genericは「型のテンプレート」だ。型を固定せずに、使う時に決める。
// ❌ any を使った非型安全な実装
function firstElement(arr: any[]): any {
return arr[0]; // 返り値の型が不明
}
// ✅ Generic で型安全に
function firstElement<T>(arr: T[]): T | undefined {
return arr[0];
}
const first = firstElement([1, 2, 3]); // 型: number | undefined
const name = firstElement(['Alice', 'Bob']); // 型: string | undefined
// 制約付き Generic
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const user = { name: "Alice", age: 30 };
const userName = getProperty(user, 'name'); // 型: string
// getProperty(user, 'invalid'); // コンパイルエラー
// Generic インターフェース
interface Repository<T> {
findById(id: string): Promise<T | null>;
findAll(): Promise<T[]>;
save(entity: T): Promise<T>;
delete(id: string): Promise<void>;
}
interface User {
id: string;
name: string;
}
// 実装
class UserRepository implements Repository<User> {
async findById(id: string): Promise<User | null> {
// ...
}
// ...
}
型ガード——実行時の型絞り込み
TypeScriptの型はコンパイル時にしか存在しない。実行時に型を確認する仕組みが型ガードだ。
// typeof 型ガード
function processInput(input: string | number) {
if (typeof input === 'string') {
// ここでは input: string として扱える
return input.toUpperCase();
}
// ここでは input: number として扱える
return input * 2;
}
// instanceof 型ガード
class Dog { bark() { return 'Woof!'; } }
class Cat { meow() { return 'Meow!'; } }
function makeSound(animal: Dog | Cat) {
if (animal instanceof Dog) {
return animal.bark(); // Dog として扱える
}
return animal.meow(); // Cat として扱える
}
// カスタム型ガード(is キーワード)
interface Admin {
role: 'admin';
permissions: string[];
}
interface NormalUser {
role: 'user';
name: string;
}
type AnyUser = Admin | NormalUser;
// 型述語(Type Predicate)
function isAdmin(user: AnyUser): user is Admin {
return user.role === 'admin';
}
function handleUser(user: AnyUser) {
if (isAdmin(user)) {
// user: Admin として扱える
console.log(user.permissions);
} else {
// user: NormalUser として扱える
console.log(user.name);
}
}
// APIレスポンスの型ガード
function isApiError(response: unknown): response is { error: string; code: number } {
return (
typeof response === 'object' &&
response !== null &&
'error' in response &&
'code' in response &&
typeof (response as any).error === 'string' &&
typeof (response as any).code === 'number'
);
}
ユーティリティ型——型変換ツール
TypeScript組み込みの型変換ユーティリティが豊富に用意されている。
interface User {
id: string;
name: string;
email: string;
password: string;
}
// Partial: すべてのプロパティをoptionalに
type PartialUser = Partial<User>;
// = { id?: string; name?: string; email?: string; password?: string; }
// Required: すべてをrequiredに
type RequiredUser = Required<PartialUser>;
// Pick: 特定のプロパティを選択
type PublicUser = Pick<User, 'id' | 'name' | 'email'>;
// = { id: string; name: string; email: string; }
// Omit: 特定のプロパティを除外
type UserWithoutPassword = Omit<User, 'password'>;
// Readonly: すべてを読み取り専用に
type ImmutableUser = Readonly<User>;
// Record: キーと値の型を指定したオブジェクト
type UserMap = Record<string, User>;
// = { [key: string]: User }
// ReturnType: 関数の戻り値の型を取得
async function fetchUser(id: string): Promise<User> { /* ... */ }
type FetchUserReturn = Awaited<ReturnType<typeof fetchUser>>;
// = User
// Parameters: 関数の引数の型を取得
type FetchParams = Parameters<typeof fetchUser>;
// = [id: string]
TypeScriptとAIコード生成
TypeScriptの型システムはAIコード生成と非常に相性が良い。
型情報がプロンプトになる
型定義が明確なコードベースでは、AIがより正確なコードを生成できる。
// 明確な型定義があれば、AIは正確な実装を生成できる
interface CreateUserRequest {
name: string;
email: string;
role: 'admin' | 'user' | 'viewer';
metadata?: Record<string, unknown>;
}
interface CreateUserResponse {
user: User;
token: string;
}
// AIへの指示: "上記の型定義を使ってユーザー作成APIを実装してください"
// → AIは型に合った実装を生成しやすい
Zodで実行時型検証
TypeScriptの型はコンパイル時のみ。外部データ(APIレスポンス・フォーム入力)にはZodでランタイム検証を追加する。
import { z } from 'zod';
// スキーマ定義(実行時検証 + TypeScript型推論)
const UserSchema = z.object({
id: z.string().uuid(),
name: z.string().min(1).max(100),
email: z.string().email(),
role: z.enum(['admin', 'user', 'viewer']),
createdAt: z.coerce.date(),
});
// TypeScript型を自動生成
type User = z.infer<typeof UserSchema>;
// 実行時にAPIレスポンスを検証
async function fetchUser(id: string): Promise<User> {
const response = await fetch(`/api/users/${id}`);
const data = await response.json();
// 型安全な検証(失敗したらエラーをスロー)
return UserSchema.parse(data);
}
まとめ
TypeScriptの型システムを使いこなすと、バグを実行前に検出し、コードの意図を明確に表現できる。
- Union型: AまたはBの型を表現。Discriminated Unionでパターンマッチを型安全に
- Intersection型: 複数の型を合成(
&) - Generic: 型をパラメータ化して再利用可能な型を作成
- 型ガード: 実行時に型を絞り込む(typeof・instanceof・カスタム型述語)
- ユーティリティ型: Partial・Pick・Omit・Readonly など組み込みの型変換ツール
- AIとの相性: 明確な型定義はAIコード生成の精度を上げる文脈になる
- Zod: ランタイムの型検証とTypeScript型推論を一元管理
型システムは「制約」ではなく「コンパイラとAIの両方があなたを助けるためのガイドレール」と捉えると、より積極的に活用できるようになる。
免責事項 — 掲載情報は執筆時点のものです。料金・機能は変更される場合があります。最新情報は各公式サイトをご確認ください。