TypeScript のインターフェース (interface)
インターフェースは、TypeScript がオブジェクトの構造を定義する中核となる手段です。これは「オブジェクトが持つべきプロパティやメソッド」を記述するものであり、実装は提供しません。
1. インターフェースの基本構文
(1) インターフェースを定義する
TYPESCRIPT
interface User {
name: string;
age: number;
email: string;
}
(2) インターフェースの使用方法
TYPESCRIPT
let user: User = {
name: "Charlie",
age: 20,
email: "xiaoming@example.com"
};
(3) 関数の引数としてのインターフェース
TYPESCRIPT
function greet(user: User): string {
return `Hello,${user.name}!How old are you this year?${user.age} years old。`;
}
console.log(greet({ name: "Diana", age: 22, email: "hong@example.com" }));
出力:
TEXT
Hello,Diana!How old are you this year?22 years old。
(4) インターフェースの記述方法
インターフェースでは、プロパティだけでなく、メソッドのシグネチャも記述できます:
TYPESCRIPT
interface Animal {
name: string;
speak(): string;
move(distance: number): void;
}
let dog: Animal = {
name: "Wangcai",
speak() { return "Woof!"; },
move(distance) { console.log(`${this.name} Moved ${distance}m`); }
};
console.log(dog.speak()); // "Woof!"
dog.move(10); // "Wangcai Moved 10m"
2. オプションのプロパティと読み取り専用プロパティ
(1) オプション属性 ?
TYPESCRIPT
interface Config {
host: string;
port: number;
debug?: boolean; // Optional
timeout?: number; // Optional
}
// Optional attributes may be omitted.
let config: Config = { host: "localhost", port: 3000 };
let config2: Config = { host: "localhost", port: 3000, debug: true };
(2) 読み取り専用プロパティ readonly
TYPESCRIPT
interface Point {
readonly x: number;
readonly y: number;
}
let point: Point = { x: 1, y: 2 };
// point.x = 10; // ❌ Read-only properties cannot be modified.
(3) ReadonlyArray とインターフェース
TYPESCRIPT
interface TodoList {
readonly name: string;
readonly items: readonly string[]; // items Both the array itself and its contents are read-only.
}
let todo: TodoList = {
name: "Today's Tasks",
items: ["Write code", "Test", "Deployment"]
};
// todo.items.push("New Task"); // ❌ Read-only arrays cannot be modified.
// todo.name = "Tomorrow's Tasks"; // ❌ Read-only properties cannot be modified.
3. インターフェースの継承 (extends)
インターフェースは、extends を通じて他のインターフェースから継承することができ、これにより型の構成や再利用が可能になります:
(1) 単一継承
TYPESCRIPT
interface Person {
name: string;
age: number;
}
interface Employee extends Person {
employeeId: string;
department: string;
}
let emp: Employee = {
name: "Charlie",
age: 28,
employeeId: "E001",
department: "Engineering Department"
};
(2) 多重継承
インターフェースは、同時に複数のインターフェースから継承することができます:
TYPESCRIPT
interface Serializable {
serialize(): string;
}
interface Loggable {
log(message: string): void;
}
interface Entity extends Serializable, Loggable {
id: number;
}
let item: Entity = {
id: 1,
serialize() { return JSON.stringify({ id: this.id }); },
log(message) { console.log(`[${this.id}] ${message}`); }
};
(3) プロパティのオーバーライド
子インターフェースは親インターフェースのプロパティ型を上書きできますが、互換性が保たれている必要があります:
TYPESCRIPT
interface Base {
data: string | number;
}
interface Derived extends Base {
data: string; // ✅ Narrowing — string is a subtype of string | number
}
▶ 例:継承を用いた階層型型の構築
TYPESCRIPT
// Basic Interfaces
interface Shape {
color: string;
}
// Extension Interface
interface Square extends Shape {
sideLength: number;
}
interface Circle extends Shape {
radius: number;
}
// Usage
let square: Square = { color: "Red", sideLength: 10 };
let circle: Circle = { color: "Blue", radius: 5 };
function describeShape(shape: Shape): string {
return `One${shape.color}the graphic`;
}
console.log(describeShape(square)); // "A red shape"
console.log(describeShape(circle)); // "A blue shape"
出力:
TEXT
A red shape
A blue shape
4. 宣言の統合
インターフェースのユニークな特徴として、同じ名前のインターフェースは、そのプロパティを自動的に統合します:
(1) 基本的なマージ
TYPESCRIPT
interface Window {
title: string;
}
interface Window {
count: number;
}
// Equivalent to:
// interface Window {
// title: string;
// count: number;
// }
let win: Window = { title: "Main Window", count: 3 };
(2) マージに関するルール
- 同じ名前のプロパティは、同じ型でなければなりません。そうでない場合、エラーが発生します。
- メソッドのシグネチャをオーバーロードされたメソッドに統合する
- 後に宣言されたインターフェースは、オーバーロード一覧の一番上に表示され(優先順位が高くなります)
TYPESCRIPT
interface Calculator {
compute(a: number, b: number): number;
}
interface Calculator {
compute(a: string, b: string): string;
}
// After the merger compute There are two overloads
let calc: Calculator = {
compute(a: any, b: any): any {
return a + b;
}
};
console.log(calc.compute(1, 2)); // 3
console.log(calc.compute("a", "b")); // "ab"
(3) 実用例:サードパーティ製型の拡張
TYPESCRIPT
// For the built-in Window Adding Custom Properties to an Interface
interface Window {
myCustomProperty: string;
}
// It is now safe to use
// window.myCustomProperty = "hello"; // ✅
💡 これが、サードパーティ製ライブラリの型を拡張する際に、
type ではなく interface を使う理由です―― type は型ユニオンをサポートしていませんが、interface はサポートしています。
5. インデックス署名とインターフェース
インデックスシグネチャはインターフェースでも使用できます:
TYPESCRIPT
interface StringMap {
[key: string]: string;
}
let translations: StringMap = {
hello: "Hello",
goodbye: "Goodbye",
thanks: "Thank you"
};
// Add a new key-value pair
translations["sorry"] = "I'm sorry";
インデックス署名と既知の属性が共存する場合、既知の属性の型は互換性がある必要があります:
TYPESCRIPT
interface Config {
[key: string]: string | number;
host: string; // ✅ string is a subtype of string | number
port: number; // ✅ number is a subtype of string | number
// debug: boolean; // ❌ boolean No string | number subtypes of
}
6. インターフェースと型エイリアスの違い
インターフェースも型もオブジェクト型を定義できますが、以下の違いがあります:
| 機能 | インターフェース | タイプ |
|---|---|---|
| オブジェクトの種類 | ✅ 主な用途 | ✅ その他の用途 |
| ステートメントの結合 | ✅ 対応 | ❌ 非対応 |
| 継承 | extends |
& クロス型 |
| ユニオン型 | ❌ 直接定義できない | ✅ type A = B | C |
| 基本型の別名 | ❌ 不可 | ✅ type ID = string |
| 計算プロパティ | ❌ 未対応 | ✅ 対応 |
| instanceof | ✅ クラスが実装している | ❌ できません |
(1) インターフェースを使用するタイミング
- オブジェクトの形状を定義する(APIの応答、設定オブジェクト、データモデル)
- 継承には
extendsが必要です - マージの宣言が必要です(サードパーティの型を拡張する場合)
- クラスが実装する(クラスがインターフェースを実装する)
(2) type の使用タイミング
- 複合タイプ:
type Status = "active" | "inactive" - 基本型の別名:
type ID = string | number - 条件付き型やマッピング型などの高度な操作
- ツールタイプの基礎となる定義
📌 推奨事項: オブジェクト型には
interface を、ユニオン型や高度な型に対する演算には type を使用してください。これら2つは互いに排他的ではなく、同じプロジェクト内で併用することができます。
❓ よくある質問
Q
interface と type、どちらを使うべきですか?A 簡単な目安として、「オブジェクトの型」を定義するには
interface を、 「型エイリアス」や「共用型」を定義するには type を使用してください。チームで既に規約が決まっている場合は、それに従ってください。90%のケースでは、この2つは同等ですので、あまり深く考えすぎないでください。Q インターフェースは、
type で定義された型を継承できますか?A はい。
interface extends は、type で定義された任意のオブジェクト型エイリアスを継承できます。逆に、type も、クロス型 & を使用してインターフェースを組み合わせることができます。この2つは完全に相互運用可能です。Q 宣言の統合にはどのようなリスクがありますか?
A 宣言の統合はインターフェースの機能の一つですが、潜在的なリスクも伴います。つまり、同じ名前の2つのインターフェースに、名前は同じだが型が異なるプロパティが含まれている場合、コンパイルエラーが発生します。実際の開発では、宣言の統合は主にサードパーティ製ライブラリの型定義(.d.ts)を拡張するために使用されます。日常的なコードでは、同じ名前のインターフェースを定義することは避けるべきです。
Q インターフェースで関数型を記述することはできますか?
A はい、できますが、一般的には
type を使うほうが自然です。interface Fn { (a: string): number } は type Fn = (a: string) => number と同等ですが、後者の方が簡潔で直感的です。関数型には type を使用することをお勧めします。📖 まとめ
- インターフェースは、オブジェクトの構造、すなわちそのプロパティ、それらの型、およびメソッドのシグネチャを定義する
?はオプションのプロパティを示し、readonlyは読み取り専用プロパティを示します。これら2つを組み合わせてreadonly x?: numberと表記することもできます。- インターフェースは
extendsを使用して継承され、単一継承と多重継承の両方をサポートしています。子インターフェースは、親インターフェースのプロパティ型を絞り込むことができます。 - マージはインターフェース固有の機能であることに注意してください。同じ名前のインターフェースは自動的にマージされ、これはサードパーティの型を拡張するために利用されます。
interfaceはオブジェクトの形状を定義するのに適しており、typeは和集合型や高度な操作に適していますが、日常の開発では、この2つはしばしば同じように使われています。
📝 練習問題
- 基本問題(難易度 ⭐):
Bookインターフェース(title、author、pages、isbn?)を定義し、2つのbookオブジェクトを作成して、それらを出力してください。なお、isbnはオプションのプロパティであることに注意してください。 - 上級問題(難易度 ⭐⭐):
Shapeベースインターフェース(color メソッドと area メソッドを含む)を定義し、次にCircleとRectangleを、それぞれ Shape を継承し、独自のプロパティを追加するように定義してください。Shape 型のパラメータを受け取り、area メソッドを呼び出す関数を記述してください。 - 課題(難易度:⭐⭐⭐):宣言を使用して、組み込みの
Array<T>インターフェースにlast(): T | undefinedメソッドを追加してください。次に、実際の配列に対してこのメソッドを呼び出し、なぜモジュール宣言が必要なのかを考えてみてください。



