TypeScript インターフェイスと型の比較
TypeScriptにおけるインターフェイスと型の比較
インターフェイスと型は、TypeScriptで型の安全性を確保するために使用される重要な概念です。どちらも変数や関数の型を指定しますが、そのアプローチが異なります。
インターフェイス
- 例
- 役割
クラスやオブジェクトが持つべきプロパティやメソッドを指定します。 - 使い方
interface
キーワードを使用して定義します。 - 定義
契約や仕様を定義するものです。
interface Person {
name: string;
age: number;
greet(): void;
}
型
- 役割
プリミティブ型(number、string、booleanなど)やオブジェクト型を指定します。 - 定義
値の構造や形状を指定します。
type PersonType = {
name: string;
age: number;
greet(): void;
};
重要な違い
- 宣言合併
インターフェイスは宣言合併が可能ですが、型は宣言合併できません。 - 継承
インターフェイスは他のインターフェイスを継承できますが、型は継承できません。 - 柔軟性
インターフェイスはオブジェクトの形状を定義し、型は値の具体的な構造を定義します。そのため、インターフェイスはより柔軟で、複数の型が同じインターフェイスを実装できます。
いつ使うか
- 既存の型を拡張したい場合
インターフェイスを使用します。 - 値の具体的な構造を指定したい場合
型を使用します。
インターフェイスと型の基本的な例
// インターフェイス
interface Person {
name: string;
age: number;
greet(): void;
}
// 型
type PersonType = {
name: string;
age: number;
greet(): void;
};
// インターフェイスの実装
class Student implements Person {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
greet() {
console.log(`Hello, my name is ${this .name}.`);
}
}
// 型の利用
let person: PersonType = {
name: 'Taro Yamada',
age: 20,
greet() {
console.log('Nice to meet you!');
}
};
解説
- 変数 person
PersonType
型で定義されており、PersonType
の構造を持つオブジェクトを代入できます。 - クラス Student
Person
インターフェイスを実装しており、name
、age
プロパティとgreet
メソッドを持っています。 - 型 PersonType
インターフェイスとほぼ同じ構造ですが、type
キーワードで定義しています。 - インターフェイス Person
名前、年齢、挨拶するメソッドgreet
を持つ人のプロトタイプを定義しています。
インターフェイスの継承と型の交差型
interface Employee extends Person {
salary: number;
}
// 型の交差型
type Teacher = Person & {
subject: string;
};
let teacher: Teacher = {
name: 'Hanako Suzuki',
age: 30,
greet() {
console.log('I am a teacher.');
},
subject: 'Math',
};
- 変数 teacher
Teacher
型で定義されており、Person
のプロパティとsubject
プロパティを持っています。 - 型の交差型
Teacher
型はPerson
型とsubject
プロパティを持つ型の交差型です。 - インターフェイスの継承
Employee
インターフェイスはPerson
インターフェイスを継承し、salary
プロパティを追加しています。
インターフェイスの宣言マージ
interface Person {
address: string;
}
// 同じ名前のインターフェイスを再定義
interface Person {
phoneNumber: string;
}
let person: Person = {
name: 'Jiro Tanaka',
age: 25,
address: 'Tokyo',
phoneNumber: '090-1234-5678',
};
- 宣言マージ
同じ名前のインターフェイスを複数定義すると、それらのプロパティが自動的にマージされます。
いつどちらを使うか
- 複雑な型の組み合わせ
型の交差型やユニオン型
インターフェイスと型は、TypeScriptで型を定義する上でどちらも重要な役割を果たします。それぞれの特性を理解し、適切な状況で使い分けることで、より安全で保守性の高いコードを書くことができます。
- インターフェイスと型の使い分けは、チームやプロジェクトのコーディング規約によって異なる場合があります。
- 上記のコード例はあくまで基本的なものです。TypeScriptでは、ジェネリクス、条件付き型など、より高度な型の定義方法も提供されています。
ジェネリクス
- 目的
型をパラメータ化し、再利用可能なコードを作成します。
function identity<T>(arg: T): T {
return arg;
}
- 解説
T
は型パラメータであり、呼び出し時に具体的な型が渡されます。これにより、任意の型の値に対して同じ関数を適用することができます。
型エイリアス
- 目的
既存の型に別の名前を付けます。
type Name = string;
type Age = number;
- 解説
Name
やAge
のように、よく使う型に別名を付けてコードの可読性を高めることができます。
型ガード
- 目的
型を判定し、それに応じた処理を行います。
function isNumber(x: any): x is number {
return typeof x === 'number';
}
- 解説
isNumber
関数は、引数がnumber
型かどうかを判定します。型ガードを使用することで、型アサーションよりも安全に型を絞り込むことができます。
型アサーション
- 目的
型チェッカーを欺いて、ある型であると仮定します。
let someValue: any = 'this is a string';
let strLength: number = (someValue as string).length;
- 解説
型アサーションは便利ですが、誤った型でアサーションを行うと実行時エラーが発生する可能性があります。
ネストされた型
- 目的
複雑なオブジェクト構造を定義します。
type Person = {
name: string;
address: {
city: string;
zipCode: string;
};
};
- 解説
オブジェクトの中にさらにオブジェクトをネストして、より詳細な型定義を行うことができます。
インデックスシグネチャ
- 目的
動的なプロパティを持つオブジェクトの型を定義します。
interface Person {
[key: string]: any;
}
- 解説
[key: string]
は、任意の文字列のプロパティを持つことを意味します。
マッピング型
- 目的
既存の型から新しい型を生成します。
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
- 解説
Readonly
は、任意の型T
のすべてのプロパティを読み取り専用にするマッピング型です。
どの方法を選ぶべきか
- 既存の型を変換
マッピング型 - 動的なプロパティ
インデックスシグネチャ - 複雑なオブジェクト構造
ネストされた型 - 型を仮定したい場合
型アサーション (慎重に使用する) - 型の判定
型ガード - 既存の型に別名を付けたい場合
型エイリアス - 汎用的な型定義
ジェネリクス
これらの方法を組み合わせることで、より柔軟かつ正確な型定義を行うことができます。
TypeScriptの型システムは非常に強力であり、様々な方法で型の定義や操作を行うことができます。それぞれの方法の特性を理解し、適切な方法を選択することで、より高品質なTypeScriptコードを作成することができます。
- 上記以外にも、TypeScriptには多くの便利な機能が提供されています。TypeScriptの公式ドキュメントを参照して、より詳細な情報を確認してください。
- TypeScriptの型システムは日々進化しています。最新の情報を常に把握しておくことが重要です。
より詳しく知りたい場合は、以下のキーワードで検索してみてください。
- TypeScript マッピング型
- TypeScript インデックスシグネチャ
- TypeScript ネストされた型
- TypeScript 型アサーション
typescript interface typescript-types