[TypeScript] interfaceの継承で、プロパティを入れたくない場合に使うOmit型

### 自己紹介
こんにちは、よわよわエンジニアのちゃったまです。
業務でTypeScriptを使っているのですが、TypeScriptの恩恵を享受しきれていない今日この頃です。

TypeScriptの恩恵の一つ、Omit型について書いてみようと思います。

---


### Omit型

interfaceを継承して、新たにinterfaceを定義したいけど、
継承させたくないプロパティがある場合ってありますよね。
<br>

例えば、以下のinterfaceで、「description」を除きつつ継承させたい場合。

```typescript

// 継承元
export interface Member {
  name: string;
  email: string;
  role: Role;
  description: string; // こいつは継承させたくない
}

```

そんな時に使うのが**Omit型**です。
**Omit型**を使用すると、次のように書くことができます。

```typescript

export interface SubMember extends Omit<Member, 'description'> {
   department: string;
}

const subMember: SubMember = {
  // descriptionは引き継がれない
  name: 'John Doe',
  email: 'john.doe@example.com',
  role: 'Admin',
  department: 'sales'
}

```


「description」が除外された形で、元のinterfaceを継承しています。
<br>

注意点として、プロパティを持たないinterfaceは定義できません。

```typescript

// これはエラーになる
export interface SubMember extends Omit<Member, 'description'> {
}

```

<br>
代わりにtypeで定義することはできます。

参考：[interfaceとtypeの違い](https://typescriptbook.jp/reference/object-oriented/interface/interface-vs-type-alias)

```typescript

export type SubMember = Omit<Member, 'description'>;

```


<br>
複数のプロパティを継承元から除外したい場合は次のように記述します。

「description」と「role」を除外する場合。

```typescript

export type SubMember = Omit<Member, 'description' | 'role'>;

const subMember: SubMember = {
  // description, roleは引き継がれない
  name: 'John Doe',
  email: 'john.doe@example.com',
};

```

----

### Omit型の活用方法

上記が一般的な使用方法です。
他にどういう使用方法があるかChatGPT-4oに聞いてみました。

#### 1. 特定のプロパティを再定義する
Omitを使用して特定のプロパティを除外し、その後にそのプロパティを再定義することで、新しい型を作成することができます。

```typescript
interface Base {
  id: string;
  name: string;
  age: number;
  role: string;
}

// idプロパティの型を変更する
type ModifiedBase = Omit<Base, 'id'> & { id: number };

const modified: ModifiedBase = {
  id: 123,
  name: 'Alice',
  age: 30,
  role: 'Developer'
};
```

これはあまり使わなそうだな・・・同じプロパティ名なのに型が違うとか混乱しそうだし
用途あるかなぁ。
<br>


#### 2. ネストされたオブジェクトのプロパティを除外する
Omitを使って、ネストされたオブジェクトのプロパティを除外することもできます。

```typescript
interface Nested {
  id: string;
  details: {
    age: number;
    address: string;
    phone: string;
  };
}

// ネストされたdetailsオブジェクトのaddressとphoneプロパティを除外する
type ModifiedNested = Omit<Nested, 'details'> & {
  details: Omit<Nested['details'], 'address' | 'phone'>
};

const modifiedNested: ModifiedNested = {
  id: '001',
  details: {
    age: 25
    // addressとphoneプロパティは存在しません
  }
};
```

うーん。これも使うかな？微妙だ。
detailsを型定義してそこでOmit型を使用した方がいいような気がするけど
短く書けるしこっちの方がいいのかな。

<br> 

#### 3. 動的なプロパティ除外
動的にプロパティを除外するためのユーティリティ型を作成することもできます。


```typescript
type DynamicOmit<T, K extends keyof T> = Omit<T, K>;

// 使用例
interface User {
  id: string;
  name: string;
  email: string;
  age: number;
}

type UserWithoutEmail = DynamicOmit<User, 'email'>;

const user: UserWithoutEmail = {
  id: '123',
  name: 'John Doe',
  age: 45
  // emailプロパティは存在しません
};
```

これはわかりやすくていいかも。
<br>

#### 4. プロパティ名のリストから除外
プロパティ名のリストを使って複数のプロパティを除外するユーティリティ型を作成することもできます。

```typescript
 type RemoveProperties<T, K extends (keyof T)[]> = Omit<T, K[number]>;

 // 使用例
 type UserWithoutEmailAndAge = RemoveProperties<User, ['email', 'age']>;

 const userWithoutEmailAndAge: UserWithoutEmailAndAge = {
  id: '123',
  name: 'John Doe'
  // emailとageプロパティは存在しません
 };
```

3の拡張版だね。
<br>


### まとめ

Omitの基本的な使用については知っていたものの、いろんな使い方があるんだなと思った。
TypeScriptマスターへの道は果てしなく続く・・・。