TypeScriptにおける type
(型エイリアス)と interface
は、どちらも型を定義するための構文で、オブジェクトやデータの構造を記述する際に使用される。
1. type
とは
type
は、型エイリアス(型の別名)を作成する構文で、特定の型や値の構造に名前を付けて再利用可能にできる。オブジェクト型だけでなく、プリミティブ型、ユニオン型、交差型、リテラル型など、幅広い型を定義可能。
特徴
- 単一定義:同じ名前での再定義は不可(上書きできない)。
-
演算子を使用:
|
(ユニオン)や&
(交差)で型を組み合わせ可能。 - ジェネリクス対応:型パラメータをサポート。
サンプルコード
// オブジェクト型の定義
type User = {
id: number;
name: string;
email?: string;
};
const user: User = { id: 1, name: "Alice" };
// ユニオン型の定義
type ID = number | string;
let userId: ID = "abc123";
// 交差型の定義
type Point = { x: number } & { y: number };
const point: Point = { x: 10, y: 20 };
// リテラル型の定義
type Status = "success" | "error";
let status: Status = "success";
ユースケース
- プリミティブ型やユニオン型を定義(例:
type Role = "admin" | "user"
)。 - 複雑な型操作(例:交差型やジェネリクス)。
- 一時的な型や単発の型定義。
2. interface
とは
interface
は、オブジェクトやクラスの構造を定義するための構文で、主にオブジェクトの形状やクラスの契約を記述。TypeScriptの構造的型システムに基づき、特定のプロパティやメソッドを持つ型を保証。
特徴
- オブジェクト/クラス特化:主にオブジェクト型やクラスの型定義に使用。
-
マージ可能:同じ名前の
interface
を複数定義すると、自動的にマージされる。 -
継承対応:
extends
で他のインターフェースを拡張可能。 -
実装対応:
implements
キーワードを使用して、インターフェースを実装。
サンプルコード
// 基本的なインターフェース
interface User {
id: number;
name: string;
email?: string;
}
const user: User = { id: 1, name: "Alice" };
// インターフェースのマージ
interface User {
role: string;
}
const admin: User = { id: 2, name: "Bob", role: "admin" }; // emailもroleも含む
// インターフェースの拡張
interface Admin extends User {
permissions: string[];
}
const superAdmin: Admin = {
id: 3,
name: "Charlie",
role: "superadmin",
permissions: ["read", "write"],
};
// クラスの実装
interface Printable {
print(): void;
}
class Document implements Printable {
print() {
console.log("Printing...");
}
}
ユースケース
- オブジェクトやクラスの構造を定義(例:APIレスポンス、コンポーネントのprops)。
- 複数のモジュールでインターフェースを拡張(マージ)。
- クラスやライブラリの型定義。
3. type
とinterface
の主な違い
比較項目 | type |
interface |
---|---|---|
再定義 | ❌ 不可 | ⚪ マージされる |
ユニオン型 | ⚪ 対応 | ❌ 非対応(オブジェクトに限られる) |
交差型(合成) | ⚪ &演算子で可能 | ⚪ extends キーワードで可能 |
プリミティブ型 | ⚪ 定義可能 | ❌ 定義不可 |
クラス実装 | ⚪ 可能 (implements不可) | ⚪ implements で明示的に連携可能 |
マージ挙動 | ❌ 上書きエラー | ⚪ 自動的に統合 |
マージの例
// interface: マージされる
interface User {
name: string;
}
interface User {
age: number;
}
const user: User = { name: "Alice", age: 25 }; // OK
// type: マージ不可
type Point = { x: number };
// type Point = { y: number }; // エラー: 再定義不可
拡張の例
// interface: extendsで拡張
interface Person {
name: string;
}
interface Employee extends Person {
job: string;
}
// type: 交差型で拡張
type PersonType = { name: string };
type EmployeeType = PersonType & { job: string };
4. 使い分け
type
を選ぶ場合
-
プリミティブ型やユニオン型を定義:
type Role = "admin" | "user"; type ID = number | string;
-
複雑な型操作(ジェネリクス、交差型、ユーティリティ型など):
type PartialUser = Partial<{ id: number; name: string }>;
- 一時的または単発の型:再利用や拡張が不要な場合。
- 柔軟性が欲しい:型エイリアスは幅広い型に対応。
interface
を選ぶ場合
-
オブジェクト型やクラスの構造を定義:
interface User { id: number; name: string; }
-
マージが必要:ライブラリやモジュール間で型を拡張。
interface Window { customProperty: string; // グローバル型を拡張 }
-
クラス実装:クラスが特定のインターフェースを満たす必要がある場合。
interface Printable { print(): void; } class Document implements Printable { print() {} }
注意点
意図しない型の競合に注意
複数のライブラリやスクリプトが同じインターフェース名を使っていると、型の競合が起きる。
// 意図しない競合例
interface Window {
customProp: string;
}
interface Window {
customProp: number; // ❌ 型の衝突が発生(エラー)
}
→ このような場合、衝突を避けるために type の使用やスコープ分離が推奨される。
まとめ
-
type
:柔軟で、ユニオン型やプリミティブ型、複雑な型操作に最適。単一定義で再定義不可。 -
interface
:オブジェクト型やクラスに特化。マージや継承が強力。 -
使い分け:オブジェクト型や拡張性なら
interface
、ユニオン型や一時的な型ならtype
。