TypeScriptで型をインターフェースとして定義する方法


TypeScript を使っていると、「型」を意識する機会が増えますよね。

その中でも「インターフェース」という仕組みは、コードを分かりやすくし、保守性を高めるための便利なツールです。

この記事では、TypeScript 初心者向けにインターフェースの定義方法や使い方をわかりやすく解説していきます。

## 1. TypeScript の型定義とは？

まず、TypeScript では変数や関数に対して「型」を定義できます。

型を定義することで、データの扱いが厳密になり、コードが予期せぬバグを防げるようになります。

例えば、次のように型を指定すると、その変数には文字列しか代入できなくなります。

```typescript
let userName: string;
```

もし、誤って数値を代入しようとするとエラーを発生させてくれます。

**型を定義するメリット**

- **コードが読みやすくなる**：型が明示されていると、後から見てもどんなデータを扱っているのか一目瞭然です。

- **エラーを未然に防ぐ**：型が正しくないとコンパイル時にエラーを出してくれるので、安心して開発できます。

- **安全にリファクタリングできる**：大規模なプロジェクトでも、型がしっかりしていれば、変更が原因のバグを減らせます。

## 2. インターフェースの基本構文

インターフェースは、オブジェクトの形を定義するための仕組みです。

オブジェクトにどんなプロパティが必要か、あらかじめ決めておくことで、コードの誤りを防ぎます。

### インターフェースの定義方法

インターフェースの基本的な定義方法は以下の通りです。

```typescript
interface User {
  name: string;
  age: number;
}
```

`User`インターフェースは「`name`が文字列、`age`が数値である」という形を定義しています。

### インターフェースを使った型定義の例

このインターフェースを使ってオブジェクトの型を定義してみましょう。

```typescript
let user: User = {
  name: 'Taro',
  age: 25,
};
```

もし、`name`を数値にしたり、`age`を省略するとエラーが発生します。

これで、データの構造が保証されるので安心ですね！

## 3. インターフェースと型エイリアスの違い

インターフェースと似たものに「型エイリアス」があります。

この 2 つは似ていますが、用途に応じて使い分けるのがポイントです。

### インターフェース vs 型エイリアス

- **インターフェース**：主にオブジェクトの形を定義するために使います。

- **型エイリアス**：任意の型に名前を付けるために使います。

例えば、型エイリアスは次のように定義できます。

```typescript
type UserAlias = {
  name: string;
  age: number;
};
```

一見すると、インターフェースと同じように見えますが、インターフェースは拡張性があり、型エイリアスはより柔軟です。

## 4. インターフェースの拡張方法

インターフェースは、他のインターフェースを「継承」して拡張することが可能です。

これにより、同じプロパティを何度も書く手間が省け、再利用性も高まります。

### インターフェースの継承

例えば、`Person`インターフェースを継承した`Employee`インターフェースを次のように定義できます。

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

interface Employee extends Person {
  employeeId: number;
}
```

`Employee`は`Person`を継承して、`name`と`age`に加えて`employeeId`プロパティも持つようになります。

こうすると、複数のインターフェースをまとめて使うことができて便利です。

## 5. オプショナルプロパティと読み取り専用プロパティ

インターフェースでは、プロパティをオプションにしたり、読み取り専用にしたりすることもできます。

### オプショナルプロパティ

プロパティをオプションにしたい場合は、`?`を使います。

```typescript
interface User {
  name: string;
  age?: number;
}
```

このように定義すると、`age`は必須ではなくなります。

```typescript
let user: User = {
  name: 'Taro',
};
```

`age`がなくてもエラーになりません。

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

プロパティを読み取り専用にするには、`readonly`を使います。

```typescript
interface User {
  readonly id: number;
  name: string;
}
```

`id`は読み取り専用のため、後から変更することができません。

## 6. インデックスシグネチャの使い方

インターフェースでは、キーが不定なプロパティを持つオブジェクトを定義することも可能です。

これを「インデックスシグネチャ」と呼びます。

### 動的なプロパティの定義

インデックスシグネチャを使うと、プロパティ名が動的に決まるオブジェクトを定義できます。

```typescript
interface Dictionary {
  [key: string]: string;
}
```

このインターフェースは、キーが文字列、値も文字列であるオブジェクトを定義します。

```typescript
let myDictionary: Dictionary = {
  apple: 'A fruit',
  car: 'A vehicle',
};
```

## 7. 関数型インターフェースの定義

インターフェースは、オブジェクトの型だけでなく、関数の型を定義することもできます。

これにより、関数の引数や戻り値に対して型をしっかり定義できます。

### 関数の型定義をインターフェースで行う

例えば、次のように関数のインターフェースを定義します。

```typescript
interface Greeter {
  (name: string): string;
}
```

この`Greeter`インターフェースは、`name`という文字列を引数にとり、文字列を返す関数を定義しています。

```typescript
let greet: Greeter = (name: string) => {
  return `Hello, ${name}`;
};
```

これで、`greet`という関数は必ず文字列を受け取り、文字列を返すことが保証されます。

## 8. インターフェースとクラスの関係

TypeScript では、インターフェースを使ってクラスの型を定義し、そのクラスが特定の構造を持つことを保証することができます。

### クラスでインターフェースを実装する

クラスにインターフェースを適用するには、`implements`キーワードを使います。

```typescript
interface Animal {
  name: string;
  sound(): void;
}

class Dog implements Animal {
  name: string;

  constructor(name: string) {
    this.name = name;
  }

  sound() {
    console.log('Woof!');
  }
}
```

このようにして、クラスがインターフェースの型に従って実装されることが保証されます。