第5章:オブジェクトとインターフェース

【TypeScript入門シリーズ】第5章:オブジェクトとインターフェース

前章では、TypeScriptにおける基本的な型と型注釈について学びました。今回はさらに進んで、オブジェクトインターフェースについて詳しく解説します。オブジェクトは、プログラミングにおいてデータや機能をまとめる非常に重要な構造であり、TypeScriptのインターフェースを使うことで、オブジェクトの構造を明確に定義し、型安全なコードを作成することができます。


4.1 オブジェクトの型定義

JavaScriptでは、オブジェクトはキーと値のペアで構成されます。TypeScriptでは、このオブジェクトに型を定義することで、オブジェクト内のプロパティの型を制約し、安全に扱うことができます。

4.1.1 基本的なオブジェクトの型定義

TypeScriptでは、オブジェクトのプロパティに型注釈をつけて型を定義します。以下の例では、personオブジェクトに対してnameは文字列、ageは数値、isStudentは真偽値という型を定義しています。

let person: {
name: string;
age: number;
isStudent: boolean;
} = {
name: "Alice",
age: 30,
isStudent: true
};

この例では、personオブジェクトが3つのプロパティ(nameageisStudent)を持ち、それぞれが指定された型であることが求められます。これにより、型の不一致によるエラーを防ぐことができます。

4.1.2 オプションプロパティ

オブジェクトのプロパティが必須でない場合、TypeScriptではオプションプロパティを使うことができます。オプションプロパティを定義するには、プロパティ名の後に?をつけます。

let car: {
brand: string;
model: string;
year?: number; // オプションプロパティ
} = {
brand: "Toyota",
model: "Corolla"
};

この例では、yearプロパティがオプションとなっており、定義しなくてもエラーになりません。オプションプロパティを使うことで、柔軟にオブジェクトの構造を設計できます。

4.1.3 読み取り専用プロパティ

TypeScriptでは、プロパティを読み取り専用にすることができます。**readonly**キーワードを使ってプロパティを定義すると、そのプロパティの値を後から変更することができなくなります。

let book: {
readonly title: string;
author: string;
} = {
title: "TypeScript入門",
author: "山田 太郎"
};

book.author = "佐藤 花子"; // OK: 書き換え可能
book.title = "JavaScript入門"; // エラー: readonlyプロパティは変更できない

この例では、titleプロパティがreadonlyとして定義されているため、後から値を変更しようとするとエラーが発生します。readonlyプロパティを使用することで、意図しない変更を防ぐことができます。


4.2 インターフェースの基本

インターフェース(interface)は、オブジェクトの構造を定義するための強力なツールです。インターフェースを使うことで、複数の場所で再利用可能な型を定義し、コードの可読性やメンテナンス性を向上させることができます。

4.2.1 インターフェースの定義

インターフェースを定義するには、interfaceキーワードを使います。インターフェースは、オブジェクトがどのようなプロパティを持つべきかを定義します。

interface Person {
name: string;
age: number;
isStudent: boolean;
}

let alice: Person = {
name: "Alice",
age: 30,
isStudent: true
};

この例では、PersonインターフェースがnameageisStudentというプロパティを持つことを定義しています。このインターフェースを使って、複数のオブジェクトで同じ型定義を再利用できます。

4.2.2 オプションプロパティとインターフェース

オブジェクトと同様に、インターフェースでもオプションプロパティを定義することができます。オプションプロパティには、?を使って定義します。

interface Car {
brand: string;
model: string;
year?: number; // オプションプロパティ
}

let myCar: Car = {
brand: "Honda",
model: "Civic"
};

このように、オプションプロパティを使って、必要に応じてプロパティの有無を指定できます。

4.2.3 読み取り専用プロパティとインターフェース

インターフェースでも、プロパティを読み取り専用にすることが可能です。readonlyを使うことで、そのプロパティが変更できないことを指定します。

interface Book {
readonly title: string;
author: string;
}

let myBook: Book = {
title: "TypeScriptガイド",
author: "山田 太郎"
};

myBook.author = "佐藤 花子"; // OK: 書き換え可能
myBook.title = "JavaScriptガイド"; // エラー: readonlyプロパティは変更できない

readonlyプロパティは、インターフェースを使う場面でも同様に有効であり、データの整合性を保つのに役立ちます。


4.3 インデックスシグネチャ

インデックスシグネチャを使うと、オブジェクトに動的にプロパティを追加できるような型を定義できます。これにより、事前にすべてのプロパティを定義するのが難しいオブジェクトに対しても、柔軟な型注釈を行うことができます。

interface StringDictionary {
[key: string]: string;
}

let myDictionary: StringDictionary = {
name: "TypeScript",
version: "4.0"
};

myDictionary.language = "English"; // OK: 動的にプロパティを追加
myDictionary.year = 2020; // エラー: number型はstring型に代入できない

この例では、StringDictionaryインターフェースは任意の文字列をキーとして、値を文字列型で持つプロパティを許容しています。これにより、事前にすべてのキーを定義しなくても柔軟にオブジェクトを扱えます。


4.4 インターフェースの拡張

TypeScriptでは、既存のインターフェースを拡張して新しいインターフェースを作成することができます。これにより、コードの再利用性が向上し、より複雑なオブジェクト構造にも対応できます。

4.4.1 単一のインターフェースを拡張

インターフェースの拡張には、extendsキーワードを使います。これにより、既存のインターフェースを拡張して新しいプロパティを追加できます。

interface Person {
name: string;
age: number;
}

interface Employee extends Person {
employeeId: number;
}

let employee: Employee = {
name: "John",
age: 35,
employeeId: 12345
};

この例では、EmployeeインターフェースがPersonインターフェースを拡張しており、新たにemployeeIdプロパティを追加しています。これにより、基本的な型を再利用しつつ、新しい要素を追加できます。

4.4.2 複数のインターフェースを拡張

TypeScriptでは、1つのインターフェースが複数のインターフェースを拡張することも可能です。これにより、さまざまなインターフェースのプロパティを組み合わせた新しいインターフェースを作成できます。

interface Person {
name: string;
age: number;
}

interface ContactInfo {
email: string;
phone: string;
}

interface Employee extends Person, ContactInfo {
employeeId: number;
}

let newEmployee: Employee = {
name: "Jane",
age: 28,
email: "jane@example.com",
phone: "123-456-7890",
employeeId: 67890
};

この例では、EmployeeインターフェースがPersonContactInfoの2つのインターフェースを拡張し、すべてのプロパティを持つオブジェクトを作成しています。このように、複数のインターフェースを組み合わせることで、柔軟な型定義が可能になります。


4.5 型エイリアスとインターフェースの違い

TypeScriptでは、型エイリアス(type alias)インターフェース(interface)の両方を使ってオブジェクトの型を定義できます。これらは似たような目的で使用されますが、いくつかの違いがあります。

4.5.1 型エイリアスの定義

型エイリアスは、typeキーワードを使って定義します。型エイリアスはインターフェースと同様にオブジェクトの型を定義できますが、他にもユニオン型や基本型など、幅広い型を定義できる点が特徴です。

type Point = {
x: number;
y: number;
};

let p: Point = {
x: 10,
y: 20
};

4.5.2 インターフェースと型エイリアスの使い分け

インターフェースは主にオブジェクトの構造を定義するために使用されます。一方、型エイリアスはオブジェクト型だけでなく、ユニオン型やリテラル型など、他の型にも適用できます。

type ID = number | string;  // ユニオン型を使った型エイリアス

一般的には、オブジェクトの型を定義する場合はインターフェース、その他の型(ユニオン型やリテラル型など)は型エイリアスを使うことが推奨されています。


まとめ

この章では、TypeScriptにおけるオブジェクトインターフェースの使い方について詳しく解説しました。オブジェクトの型定義により、型安全にデータを扱うことができ、インターフェースを使うことで、コードの再利用性や保守性が向上します。オプションプロパティ、読み取り専用プロパティ、インデックスシグネチャなど、TypeScriptの柔軟な機能を活用することで、より複雑なオブジェクト構造にも対応できるようになります。