TypeScriptのユーティリティ型を理解する

# はじめに

今回は、TypeScriptが提供する主要なユーティリティ型について解説します。

## ユーティリティ型とは

> ユーティリティ型(utility type)は、型から別の型を導き出してくれる型です。functionが実行時の世界の関数だとしたら、ユーティリティ型は型の世界の関数といったイメージです。

参照: [サバイバルTypeScript](https://typescriptbook.jp/reference/type-reuse/utility-types)

基本的に、`ユーティリティ型<型>`という形で使用します。

ユーティリティ型を使いこなせるようになるのは、TypeScript初心者を抜け出すための重要なステップだと思いますので興味のある方はぜひ学習してみてください。

## Required / Partial

`Required`と`Partial`は、オブジェクト型を受け取り、それぞれ全てのプロパティを必須にした型と、全てのプロパティを省略可能にした型を返します。

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

type RequiredUser = Required<User>;
// type RequiredUser = {
//     name: string;
//     age: number;
// }

type PartialUser = Partial<RequiredUser>;
// type PartialUser = {
//     name?: string | undefined;
//     age?: number | undefined;
// }
```

(コメントアウト部はホバーしたときに表示される型です)

元々User型のプロパティは全て省略可能でしたが、`Required`を使うことで全てのプロパティが必須になりました。また、そこに重ねる形で`Partial`を使うことで全てのプロパティが省略可能になりました。

## Readonly

`Readonly`は、オブジェクト型を受け取り、全てのプロパティを読み取り専用にした型を返します。

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

type ReadonlyUser = Readonly<User>;

const user: ReadonlyUser = {
  name: 'Taro',
  age: 20,
};
// type ReadonlyUser = {
//    readonly name: string;
//    readonly age: number;
// }

user.name = 'Jiro'; // Error: Cannot assign to 'name' because it is a read-only property.
```

`Readonly`を使うことで全てのプロパティが読み取り専用になりました。
その結果、`user.name = 'Jiro';`のようにプロパティに値を代入しようとするとエラーが発生してくれるようになります。

注意点として、一定の条件下では`Readonly`を使ってもプロパティの書き換えが可能になることがあるので、注意が必要です。

参考: https://typescript-jp.gitbook.io/deep-dive/type-system/readonly

## Record

`Record`は、キーの型と値の型を受け取り、その型を持つ全てのプロパティを持つ型を返します。
キーの型は`string`や`number`などのプリミティブ型、`symbol`型とそれらのリテラル型を指定することができます。

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

type UserRecord = Record<string, User>;

const users: UserRecord = {
  '1': { name: 'Taro', age: 20 },
  '2': { name: 'Jiro', age: 30 },
};
// type UserRecord = {
//     [x: string]: User;
// }

type RGB = 'R' | 'G' | 'B';
type Color = Record<RGB, number>;
// type Color = {
//     R: number;
//     G: number;
//     B: number;
// }

const color: Color = {
  R: 255,
  G: 255,
  B: 255,
};
```

## Pick / Omit

PickとOmitは、オブジェクト型を受け取り、それぞれ一部のプロパティを抽出した型と、一部のプロパティを除外した型を返します。
記法は`Pick<元の型, '抽出するプロパティ名'>`と`Omit<元の型, '除外するプロパティ名'>`です。

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

type UserName = Pick<User, 'name'>;
// type UserName = {
//     name: string;
// }

type UserWithoutName = Omit<User, 'name'>;
// type UserWithoutName = {
//     age: number;
// }

// リテラルを使って複数個のプロパティを指定することもできます
type PickNameAndAge = Pick<User, 'name' | 'age'>;
// type pickNameAndAge = {
//     name: string;
//     age: number;
// }

type OmitNameAndAge = Omit<User, 'name' | 'age'>;
// type OmitNameAndAge = {}
```

## Exclude / Extract

`Exclude`と`Extract`は、ユニオン型を受け取り、それぞれ一部の型を除外した型と、一部の型を抽出した型を返します。
Pick / Omitのリテラル版と考えると理解しやすいかもしれません。

記法は`Exclude<元の型, 除外する型>`と`Extract<元の型, 抽出する型>`です。

```typescript
type Cats = '三毛猫' | '黒猫' | '白猫' | '茶トラ' | 'アメショ';

type NotBlackCats = Exclude<Cats, '黒猫'>;
// type NotBlackCats = "三毛猫" | "白猫" | "茶トラ" | "アメショ"

type BlackCats = Extract<Cats, '黒猫'>;
// type BlackCats = "黒猫"

type CatsWithoutBlackAndWhite = Exclude<Cats, '黒猫' | '白猫'>;
// type CatsWithoutBlackAndWhite = "三毛猫" | "茶トラ" | "アメショ"

type CatsOnlyBlackAndWhite = Extract<Cats, '黒猫' | '白猫'>;
// type CatsOnlyBlackAndWhite = "黒猫" | "白猫"
```

## NonNullable

`NonNullable`は、型を受け取り、その型から`null`と`undefined`を除外した型を返します。

```typescript
type UserName = string | null | undefined;

type NonNullableUserName = NonNullable<UserName>;
// type NonNullableUserName = string
```

## ReturnType

`ReturnType`は、関数型を受け取り、その関数の戻り値の型を返します。

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

function createUser(name: string, age: number): User {
  return { name, age };
}

type CreateUserReturnType = ReturnType<typeof createUser>;
// type CreateUserReturnType = {
//     name: string;
//     age: number;
// }
```

## Awaited

`Awaited`は、Promise型を受け取り、そのPromiseの中身の型を返します。

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

type PromiseUser = Promise<User>;
// type PromiseUser = Promise<User>

type AwaitedPromiseUser = Awaited<PromiseUser>;
// type AwaitedPromiseUser = {
//    name: string;
//    age: number;
// }
```

`Awaited`は`ReturnType`と組み合わせて使うことが多いです。

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

async function fetchUser(): Promise<User> {
  return { name: 'Taro', age: 20 };
}
// function fetchUser(): Promise<User>

type FetchUserAwaited = Awaited<ReturnType<typeof fetchUser>>;
// type FetchUserAwaited = {
//     name: string;
//     age: number;
// }
```

## InstanceType

`InstanceType`は、コンストラクタ関数型を受け取り、そのコンストラクタ関数のインスタンスの型を返します。

```typescript
class User {
  name: string;
  age: number;

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

type UserInstance = InstanceType<typeof User>;
// type UserInstance = User
```

## Parameters

`Parameters`は、関数型を受け取り、その関数の引数の型をタプルで返します。

```typescript
function createUser(name: string, age: number) {
  return { name, age };
}

type CreateUserParameters = Parameters<typeof createUser>;
// type CreateUserParameters = [name: string, age: number]
```

## UpperCase / LowerCase

`UpperCase`と`LowerCase`は、文字列型を受け取り、その文字列を大文字にした型と小文字にした型を返します。

```typescript
type UppercaseName = Uppercase<'taro'>;
// type UppercaseName = "TARO"

type LowercaseName = Lowercase<'TARO'>;
// type LowercaseName = "taro"
```

## Capitalize / Uncapitalize

`Capitalize`と`Uncapitalize`は、文字列型を受け取り、その文字列の最初の文字を大文字にした型と小文字にした型を返します。

```typescript
type CapitalizedName = Capitalize<'taro'>;
// type CapitalizedName = "Taro"

type UncapitalizedName = Uncapitalize<'Taro'>;
// type UncapitalizedName = "taro"
```

## おわりに

最後まで読んでいただきありがとうございます！

TypeScriptのユーティリティ型は、型を操作するための非常に強力な機能です。
ぜひ、これらのユーティリティ型を使って普段のコーディングを効率化してみてください！

## 参考

https://www.typescriptlang.org/docs/handbook/utility-types.html