TypeScriptの型上級チートシート

Original article: https://www.ibrahima-ndaw.com/blog/advanced-typescript-cheat-sheet/

以下はIbrahima Ndaw( [Twitter](https://twitter.com/ibrahima92_) / [GitHub](https://github.com/ibrahima92) / [LinkedIn](https://www.linkedin.com/in/ibrahima-ndaw/) / [Webサイト](https://www.ibrahima-ndaw.com/) )によるTypeScriptの解説、[Advanced TypeScript Types cheat sheet (with examples)](https://www.ibrahima-ndaw.com/blog/advanced-typescript-cheat-sheet/)の日本語訳です。
リンクなどは元記事のままであり、和訳にあたり変更していません。


# Advanced TypeScript Types cheat sheet (with examples)

TypeScriptは型付き言語であり、変数、関数の引数および返り値、オブジェクトのプロパティに型を指定することが可能です。

この記事では、TypeScriptの型の高度な使い方を例示付きで紹介します。

## Sorry for the interrupt!

TypeScriptを総合的に学びたい人には、こちらのベストセラーコースを強くお勧めします。
[Understanding TypeScript - 2020 Edition](https://click.linksynergy.com/deeplink?id=o1JCNdqL0gw&mid=39197&murl=https%3A%2F%2Fwww.udemy.com%2Fcourse%2Funderstanding-typescript%2F)
これはアフィリエイトリンクなので、よかったら応援してね。


## Intersection Types

交差型とは、複数の型をひとつに結合した型です。
すなわち、型Aと型B、もしくはさらに他の型をマージして、それら全てのプロパティを持ったひとつの型を得ることができます。

```ts
type LeftType = {
  id: number
  left: string
}

type RightType = {
  id: number
  right: string
}

type IntersectionType = LeftType & RightType

function showType(args: IntersectionType) {
  console.log(args)
}

showType({ id: 1, left: "test", right: "test" })
// Output: {id: 1, left: "test", right: "test"}
```

見てのとおり、交差型はLeftTypeとRightType両方の要素を持っています。
交差型をつくるには`&`で結合するだけです。


## Union Types

Union型は、与えられた型のうち何れかの型となることができます。

```ts
type UnionType = string | number

function showType(arg: UnionType) {
  console.log(arg)
}

showType("test")
// Output: test

showType(7)
// Output: 7
```

関数`showType`は、string型もしくはnumber型いずれかの値を引数として受け付けることができます。


## Generic Types

ジェネリック型とは、与えられた型を再利用する手段です。
引数の型を変数のようにキャプチャすることができます。

```ts
function showType<T>(args: T) {
  console.log(args)
}

showType("test")
// Output: "test"

showType(1)
// Output: 1
```

ジェネリック型を生成するには、関数名に`<>`で括った型名`T` ( 実際は任意の名前でよい ) を指定します。
以下に、関数`showType`を異なる型で呼び出す例を示します。

```ts
interface GenericType<T> {
  id: number
  name: T
}

function showType(args: GenericType<string>) {
  console.log(args)
}

showType({ id: 1, name: "test" })
// Output: {id: 1, name: "test"}

function showTypeTwo(args: GenericType<number>) {
  console.log(args)
}

showTypeTwo({ id: 1, name: 4 })
// Output: {id: 1, name: 4}
```

ジェネリック型Tを受け取るインターフェイスGenericTypeを定義しました。
これは再利用可能なので、ひとつめのGenericTypeはstring型の値を受け取り、ふたつめはnumber型を受け取っています。


```ts
interface GenericType<T, U> {
  id: T
  name: U
}

function showType(args: GenericType<number, string>) {
  console.log(args)
}

showType({ id: 1, name: "test" })
// Output: {id: 1, name: "test"}

function showTypeTwo(args: GenericType<string, string[]>) {
  console.log(args)
}

showTypeTwo({ id: "001", name: ["This", "is", "a", "Test"] })
// Output: {id: "001", name: Array["This", "is", "a", "Test"]}
```

ジェネリック型を複数渡すこともできます。
上の例では二つのジェネリック型TとUを渡しています。
interfaceを使用することで、異なる型の引数を渡す関数が提供できるようになりました。


## Utility Types

TypeScriptでは、型を容易に操作することができるように便利な組込ユーティリティ型が提供されています。
これらを使うときは、変換したい型を`<>`に入れて渡します。

### Partial

・`Partial<T>`

Partial型は、該当する型の全てのプロパティをオプショナルにすることができます。
これはすなわち、全てのフィールドに`?`を追加するようなものです。

```ts
interface PartialType {
  id: number
  firstName: string
  lastName: string
}

// firstNameがstringからstring?になる
function showType(args: Partial<PartialType>) {
  console.log(args)
}

showType({ id: 1 })
// Output: {id: 1}

showType({ firstName: "John", lastName: "Doe" })
// Output: {firstName: "John", lastName: "Doe"}
```

関数`showType()`の引数として`PartialType`型を渡していますが、プロパティをオプショナルにするために`Partial`ユーティリティ型を通しています。
これだけで、`PartialType`型の全ての値がオプショナルになりました。


### Required

・`Required<T>`

`Partial`型とは「逆に、`Required`型は全てのプロパティを必須にします。

```ts
interface RequiredType {
  id: number
  firstName?: string
  lastName?: string
}

// firstNameがstring?からstringになる
function showType(args: Required<RequiredType>) {
  console.log(args)
}

showType({ id: 1, firstName: "John", lastName: "Doe" })
// Output: { id: 1, firstName: "John", lastName: "Doe" }

showType({ id: 1 })
// Error: Type '{ id: number: }' is missing the following properties from type 'Required<RequiredType>': firstName, lastName
```

`Required`ユーティリティ型を通すことによって、オプショナルであるはずの`RequiredType`型の全ての値が必須になります。
プロパティを省略した場合、TypeScriptはエラーを発生させます。

### Readonly

・`Readonly<T>`

`Readonly`ユーティリティ型は、全てのプロパティを変更不可能にします。

```ts
interface ReadonlyType {
  id: number
  name: string
}

function showType(args: Readonly<ReadonlyType>) {
  args.id = 4
  console.log(args)
}

showType({ id: 1, name: "Doe" })
// Error: Cannot assign to 'id' because it is a read-only property.
```

`Readonly`ユーティリティ型によって、`ReadonlyType`型の全ての値は再割り当て不能になります。
いずれかのフィールドに新しい値を設定しようとすると、エラーになります。

もっと単純に、プロパティの前に`readonly`キーワードを付けて再割り当て不能にすることもできます。

```ts
interface ReadonlyType {
  readonly id: number
  name: string
}
```

### Pick

・`Pick<T, K>`

`Pick`ユーティリティ型は、元の型からいくつかのプロパティを選んで新たな型を生成します。

```ts
interface PickType {
  id: number
  firstName: string
  lastName: string
}

// PickTypeのうちfirstName,lastNameだけを使った新たな型
function showType(args: Pick<PickType, "firstName" | "lastName">) {
  console.log(args)
}

showType({ firstName: "John", lastName: "Doe" })
// Output: {firstName: "John"}

showType({ id: 3 })
// Error: Object literal may only specify known properties, and 'id' does not exist in type 'Pick<PickType, "firstName" | "lastName">'
```

これまでに見てきたユーティリティ型とは少々異なる構文で、二つの引数が必要です。
Tは元の型、そしてKは抽出したいプロパティです。
複数のフィールドを`|`で区切ることによって、複数のフィールドを抽出することも可能です。


### Omit

・`Omit<T, K>`

`Omit`ユーティリティ型は`Pick`のちょうど反対で、必要なプロパティを選ぶのではなく不要なプロパティを削除します。

```ts
interface PickType {
  id: number
  firstName: string
  lastName: string
}

// PickTypeのうちfirstName,lastNameを使わない新たな型
function showType(args: Omit<PickType, "firstName" | "lastName">) {
  console.log(args)
}

showType({ id: 7 })
// Output: {id: 7}

showType({ firstName: "John" })
// Error: Object literal may only specify known properties, and 'firstName' does not exist in type 'Pick<PickType, "id">'
```

`Pick`と同じ使い方で、元となる型から削除するプロパティを指定します。


### Extract

・`Extract<T, U>`

`Extract`ユーティリティ型は、T型のプロパティのうち、U型に代入可能なプロパティを抽出します。
2つの型に共通するプロパティを取り出すと考えてよいでしょう。


```ts
interface FirstType {
  id: number
  firstName: string
  lastName: string
}

interface SecondType {
  id: number
  address: string
  city: string
}

type ExtractType = Extract<keyof FirstType, keyof SecondType>
// Output: "id"
```

上の例では、2つの型が同じプロパティ`id`を持っています。
この型にExtractを使用することで、両方に共通するプロパティ`id`を取り出すことができます。
共通するプロパティが複数存在する場合は、その全てが抽出されます。


### Exclude

・`Exclude<T, U>`

`Exclude`ユーティリティ型は、T型のプロパティのうち、U型に代入可能なプロパティを除外した型を生成します。

```ts
interface FirstType {
  id: number
  firstName: string
  lastName: string
}

interface SecondType {
  id: number
  address: string
  city: string
}

type ExcludeType = Exclude<keyof FirstType, keyof SecondType>
// Output; "firstName" | "lastName"
```

FirstTypeのプロパティ`firstName`・`lastName`はSecondTypeには存在しないため、Excludeで取り出すことができます。
SecondTypeの値`address`や`city`は出てきません。

### Record

・`Record<K,T>`

このユーティリティは、T型の値の集合を作るために役立ちます。
ある型のプロパティを別の型にマッピングする際に、非常に便利です。

```ts
interface EmployeeType {
  id: number
  fullname: string
  role: string
}

let employees: Record<number, EmployeeType> = {
  0: { id: 1, fullname: "John Doe", role: "Designer" },
  1: { id: 2, fullname: "Ibrahima Fall", role: "Developer" },
  2: { id: 3, fullname: "Sara Duckson", role: "Developer" },
}

// 0: { id: 1, fullname: "John Doe", role: "Designer" },
// 1: { id: 2, fullname: "Ibrahima Fall", role: "Developer" },
// 2: { id: 3, fullname: "Sara Duckson", role: "Developer" }
```

`Record`の動作はシンプルです。
上の例ではキーの型が`number`なので、0,1,2と数値を指定しています。
値は`EmployeeType`型となっているので、id・fullname・roleを持つオブジェクトが必要です。
文字列を与えたりするとエラーになります。


### NonNullable

・`NonNullable<T>`

型Tからnullとundefinedを消し去ります。

```ts
type NonNullableType = string | number | null | undefined

function showType(args: NonNullable<NonNullableType>) {
  console.log(args)
}

showType("test")
// Output: "test"

showType(1)
// Output: 1

showType(null)
// Error: Argument of type 'null' is not assignable to parameter of type 'string | number'.

showType(undefined)
// Error: Argument of type 'undefined' is not assignable to parameter of type 'string | number'.
```

NonNullableユーティリティは、引数からnullとundefinedを消し去った新たな型を生成します。
その型にNullableな値を渡すと、TypeScriptはエラーを発します。

なお、`tsconfig`ファイルに`strictNullChecks`を指定すると、自動的に全ての型にNonNullableが適用されます。


## Mapped types

Mapped typesは、既存のモデルを流用しつつ、各プロパティを新しい型に変更することができるようになります。
先に解説したユーティリティ型も、一部の実体はMapped typesです。

```ts
type StringMap<T> = {
  [P in keyof T] : string
}

function showType(arg: StringMap<{ id: number; name: string }>) {
  console.log(arg)
}

showType({ id: 1, name: "Test" })
// Error: Type 'number' is not assignable to type 'string'.

showType({ id: "testId", name: "This is a Test" })
// Output: {id: "testId", name: "This is a Test"}
```

`StringMap<>`は、渡された型が何であれ、とりあえず文字列型にします。
従って、`showType`に渡す型はnumberではなくstringとなり、number型を渡した場合はエラーが出ることになります。


## Type Guards

Type Guardsを使うと、変数やオブジェクトの型を演算子で判定することができます。

### typeof

```ts
function showType(x: number | string) {
  if (typeof x === "number") {
    return `The result is ${x + x}`
  }
  throw new Error(`This operation can't be done on a ${typeof x}`)
}

showType("I'm not a number")
// Error: This operation can't be done on a string

showType(7)
// Output: The result is 14
```

上記コードでは、`typeof`を用いて受け取った引数の型をチェックしています。
条件で型ガードすることができました。


### instanceof

```ts
class Foo {
  bar() {
    return "Hello World"
  }
}

class Bar {
  baz = "123"
}

function showType(arg: Foo | Bar) {
  if (arg instanceof Foo) {
    console.log(arg.bar())
    return arg.bar()
  }

  throw new Error("The type is not supported")
}

showType(new Foo())
// Output: Hello World

showType(new Bar())
// Error: The type is not supported
```

`typeof`の例と同様、こちらでは引数の型がFooクラスであるかをチェックします。

### in

```ts
interface FirstType {
  x: number
}
interface SecondType {
  y: string
}

function showType(arg: FirstType | SecondType) {
  if ("x" in arg) {
    console.log(`The property ${arg.x} exists`)
    return `The property ${arg.x} exists`
  }
  throw new Error("This type is not expected")
}

showType({ x: 7 })
// Output: The property 7 exists

showType({ y: "ccc" })
// Error: This type is not expected
```


オブジェクトにプロパティが存在するかどうかは`in`でチェックすることができます。


## Conditional Types

以下では複数の型をテストし、結果に応じてその片方を選択しています。

```ts
type NonNullable<T> = T extends null | undefined ? never : T
```

`NonNullable`ユーティリティ型は、型がnullであるかをチェックし、結果に応じて異なる処理をしています。
この例ではJavaScriptの三項演算子を使用していることに注意してください。




# コメント欄

[dev.to](https://dev.to/ibrahima92/advanced-typescript-types-cheat-sheet-with-examples-5414)のコメント欄

「わかりやすくてよい記事。GJ！」
「簡潔によくまとめられてる。」
「[独自のタイプガードの可能性を追求した](https://res.cloudinary.com/practicaldev/image/fetch/s--xlOxHMAZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/120iap5muswfs0j3w533.png)。」
「もっとはやくPartialを知りたかった。わざわざ自力で書いてたよ。`export type ObjectWithOptionalProps<T> = { [key in keyof T]?: T[key] };`」
「Maybe型`type Maybe<T> = T | null;`をよく使ってる。」「普通にオプショナル`function foo(bar?: string)`でよくない？」「Maybe型の場合は値が必須というところが異なるよ。」
「TypeScript複雑になりすぎてきたような」「ほとんどは糖衣構文なので使わなければいいだけだぞ」「せやな」
「行末にセミコロンを忘れるな」「セミコロンは必須ではないし使わない方が好き」「ない方がすっきりしててよい」「[Google様に逆らうなら](https://google.github.io/styleguide/jsguide.html#formatting-semicolons-are-required)不要だね」


# 感想

[TypeScript Guideシリーズ](https://dev.to/ibrahima92/series/7319)の2番目で、主に型の変換について記述された記事です。
他のシリーズも通して読むことで、TypeScriptへの理解がより深まることでしょう。
参考になったらぜひ[コーヒーを買って](https://www.buymeacoffee.com/koIR0wM)あげましょう。

実際に手を動かしてみたいという場合は、ちょうど最近[日本語解説記事が出た](https://qiita.com/ryo2132/items/925b96838dd8cca7cebd) [type-challenges](https://github.com/type-challenges/type-challenges)などを試してみるとよいかもしれません。

しかし型に関するいろいろな機能はありますが、実際使うかといったら個人的にはほとんど使っていません。
私はフロントエンドエンジニアではないので本格的にがっつり使ってないからということもありますが、極めてふつーに、型を定義してそれを直接使う程度のことしかやっていませんし、それで不足を感じることもあまりありません。
日頃使ってるのはせいぜいUnion型くらいです。
OmitとかExcludeとか何に使うのか全くわからん。
みんなこういうのバキバキ使いこなしてるんですかね？