🙆‍♀️

[TypeScript]色々な型表現

2023/05/06に公開

はじめに

TypeScriptには様々なデータ型の活用法があるのでメモとして残していく
TypeScriptの4.9.5で確認

型ガード

条件によって特定の型を絞り込むことができる

プリミティブ型でよく使われる型ガード

const typeGuard = (x: string | number) => {
  if (typeof x === 'string') {
    // xはstring型だと推論できる
  } else if (typeof x === 'number') {
    // xはnumber型だと推論できる
  }
}

オブジェクト型でよく使われる型ガード

in演算子を使用した型ガード。大きく2種類。

1 構造的部分型による意図しない挙動をするパターン

下記コードの①のhogefugaプロパティを持ったxが構造的部分型により代入できてしまう。つまり、「Fuga型の時はfugaプロパティしか持っていない」、「Hoge型の時はhogeプロパティしか持っていない」というわけではない。そのようなケースではif文で理想の分岐ができないケースもあるので注意

type Fuga = {
  fuga: string;
}

type Hoge = {
  hoge: string;
}

const typeGuard = (x: Fuga | Hoge) => {
  if ('hoge' in x) {
    // xがHoge型の時のみ通ってほしいが、そうならないパターンもある
  } else if ('fuga' in x) {
    // xがFuga型の時のみ通ってほしいが、そうならないパターンもある
  }
}

const x = {
  hoge: 'hoge',
  fuga: 'fuga',
}
// ①
const fuga: Fuga | Hoge = x;
typeGuard(fuga)

2 構造的部分型による意図しない挙動を避ける型ガード(タグ付きUnion型を使ったオブジェクト型の型ガード)

Union型を構成する型に共通プロパティを追加。それを利用してif分岐を行う

type Hoge = {
  type: 'hoge';
  hoge: string;
}

type Fuga = {
  type: 'fuga';
  fuga: string;
}

// `type`により意図した条件分岐ができる
const typeGuard = (x: Fuga | Hoge) => {
  if (x.type === 'fuga') {
    // Fuga型
  } else if (x.type === 'hoge') {
    // Hoge型
  }
}

網羅性チェック

never型を用いて網羅性チェックができる
never型にはどんな値もセットできないことを利用した方法

type Fuga = {
  type: 'fuga';
  fuga: string;
}

type Hoge = {
  type: 'hoge';
  piyo: string;
}

type HogeFuga = Hoge | Fuga;

const exhaustiveCheck = (x: HogeFuga) => {
  if (x.type === 'fuga') {
    // 何か処理
  } else if (x.type === 'hoge') {
    // 何か処理
  } else {
    const _exhaustiveCheck: never = x;
  }
}

新しい型を追加するとエラーとなり、考慮漏れに気付ける

type Piyo = {
  type: 'piyo';
  piyo: string;
}

// Piyoを追加
type HogeFugaPiyo = Hoge | Fuga | Piyo;

const exhaustiveCheck = (x: HogeFugaPiyo) => {
  if (x.type === 'fuga') {
    // 何か処理する
  } else if (x.type === 'hoge') {
    // 何か処理する
  } else {
    // Type 'Piyo' is not assignable to type 'never'. というエラーとなる
    const _exhaustiveCheck: never = x;
  }
}

インデックスシグネチャ

インデックスでオブジェクトのプロパティとその型を定義する機能

type indexSignature = {
  [key: string]: string;
};

デメリット

存在しないプロパティに対する推論に危険性がある
下記コードの場合string型で推論されてしまう

const x: indexSignature = {}
// `x.id`は実際には存在しないが、indexSignature[string]: string のようにstring型で推論されてしまう
console.log('x', x.id)

上記デメリットの対策

tsconfig.jsoncompilerOptionsnoUncheckedIndexedAccessを追加してtrueを設定する
すると、undefinedが推論される

const x: indexSignature = {}
// indexSignature[string]: string | undefined undefinedが推論されるようになる
console.log('x', x.id)

Conditional Typesとnever型を組み合わせたUnion型のフィルタリング

type Fuga = {
  type: 'fuga';
  fuga: string;
}

type Hoge = {
  type: 'hoge';
  hoge: string;
}

type Piyo = {
  type: 'piyo';
  piyo: string;
}

type HogeFuga = Hoge | Fuga;

type Filter<T> = T extends HogeFuga ? T : never;

// Hoge | Fuga となる
type HogeFugaFilter = Filter<Hoge | Fuga | Piyo>

?修飾子が付与されたプロパティとundefinedのUnion型のUnion型の違い

例えば下記2つの違い

type Hoge = {
  hoge?: string;
}

type Hoge = {
  hoge: string | undefined;
}

?修飾子の場合はプロパティを明示的に宣言しなくても問題ない

type Hoge = {
  hoge?: string;
}

const x: Hoge = {}

undefinedのUnion型の場合hogeプロパティを省略できない

type Hoge = {
  hoge: string | undefined;
}

// Property 'hoge' is missing in type '{}' but required in type 'Hoge'
const x: Hoge = {}

オブジェクトのプロパティをUnion型で表現

const hogefuga = {
  'hoge': 'hogehoge’
  'fuga': 'fugafuga’
}

// let obj: "hoge" | "fuga"
let obj: keyof typeof hogefuga;

オブジェクトのプロパティのデータ型を取得

あるオブジェクトのプロパティの型をUnion型で表現

const hogefuga = {
  'hoge': 'hogehoge',
  'fuga': 'fugafuga'
} as const;

// type Hoge = "hogehoge" | "fugafuga"
type Hoge = typeof hoge[keyof typeof hogefuga]

配列の値を型として設定

const arr = [
  {
    hoge: 'hoge'
  },
];
type Hoge = typeof arr[number]

// 上記は以下のように型定義される
// type Hoge = {
//   hoge: string;
// }
const colors = ['red', 'blue', 'yellow'] as const;
type Color = typeof colors[number];

// 上記は下記のように配列の中身をUnion型で取得して型定義される
// type Color = "red" | "blue" | "yellow"

Union型でenumのような表現

const Color = {
  RED: 'red',
  BLUE: 'blue',
  YELLOW: 'yellow'
} as const;

type ColorType = typeof Color[keyof typeof Color]

const color: ColorType = Color.BLUE;

おわりに

データ型の表現・使い方は色々あるので使いながら慣れていく

Discussion