notebook

都内でWEB系エンジニアやってます。

TypeScriptの組み込み型関数

今回はTypeScriptの組み込み型関数について ※3.4時点

型をしっかり付け始めていろいろと直面する問題が多くそのたびに調べていった結果TypeScriptの組み込み型関数的なものが用意されていることを知りました

これを知らずに自前で型を作ろうとしてむだに考え込んでしまったりとかありそうなので(実際にあった)ある程度把握する

また、ここにある関数を参考に新たになにか作ることもできそうだと思います

TypeScript/lib.es5.d.ts at master · microsoft/TypeScript

github.com

Partial

定義はこちら

既存の型のすべてが必須ではなくなります

サンプルのとおりですね

interface Hoge {
  h1: number;
  h2: string;
  h3: boolean;
}
// Partialを通すことで{h1?: number, h2?: string, h3?: boolean}の型が生成される
// 全てのキーが必須ではなくなる
const hoge: Partial<Hoge> = {
  h1: 1,
  h2: 'hoge'
}; // h3なくてもok

Required

定義はこちら

Partialと反対ですべてのキーが必須になります

  interface Hoge {
    h1: number;
    h2?: string;
  }

  const a: Hoge = {
    h1: 1
  };
  // 全てのキーが必須となる
  // {h1: number, h2: string} の型が生成される
  const hoge: Required<Hoge> = {
    h1: 1,
    h2: 'a'
  } // ok h1, h2のうち1つでも存在しない場合はNG

Readonly

定義はこちら

interface Hoge {
  h1: number;
  h2: string;
}

const a: Hoge = {
  h1: 1,
  h2: 'hoge'
};
a.h1 = 2; // ok

const b: Readonly<Hoge> = {
  h1: 1,
  h2: 'a'
};

b.h1 = 2; // Error

見ての通りReadonlyなので各要素を書き換えできなくなる型が生成されます

個人的には基本的に常にReadonlyを付けておいたほうが良いと思います

これはネストしたハッシュに対してReadonlyにしたらよさそう?!とおもい調べてみたらすでにそういうライブラリもあるようですね

immutability - DeepReadonly Object Typescript - Stack Overflow stackoverflow.com

krzkaczor/ts-essentials: All basic TypeScript types in one place 🤙 github.com

Pick

定義はこちら

interface Hoge {
  a: number;
  b: string;
  c: number;
}

interface Fuga {
  a: number;
  b: string;
}

const a: Hoge = {
  a: 1,
  b: 'hoge',
  c: 3
};

// keyof Fuga -> a | b
// Pick<Hoge, keyof Fuga> -> {a: number, b: string}
// Hogeからa,bが取り出された新たな型が生成される
const b: Pick<Hoge, keyof Fuga> = {
  a: 1,
  b: 'hoge',
  c: 2 // Error
};

個人的に一番良く使いそうだとおもうPickです

ある型から特定のキーだけ抜き出した型を生成するための関数です

既存コードで「だいたい同じだけど少しだけ違う」みたいなパターンでつかえそうです

Record

定義はこちら

type HogeRecord = Record<'h1'|'h2', string>;

// {h1: string, h2: string}の型が生成される
const hoge: HogeRecord = {
  h1: 'hoge',
  h2: 'fuga',
};

最初上記の使いみちのみと勘違いしてとくに使いみちが。。。と思っていたのですが下記のような使い方もできるようです

type FugaRecord = Record<'hoge'|'fuga'|'piyo', {id: number, name: string}>
const samples: FugaRecord = {
  hoge: {id: 1, name: 'hoge'},
  fuga: {id: 2, name: 'fuga'},
  piyo: {id: 3, name: 'piyo'}
}; // ok
// hoge,fuga,piyo以外のキーを足すとエラー
// オブジェクトにid,name以外を足すとエラー
// idを数値以外、nameを文字列以外にするとエラー

interface ReportRow {
  id: number;
  name: string;
}
type Category = 'c1'|'c2';
type ReportRecord = Record<Category,ReportRow[]>

const rows: ReportRecord = {
  c1: [{id: 1, name: 'hoge'}, {id: 2, name: 'fuga'}],
  c2: [{id: 3, name: 'piyo'}],
  c3: [{id: 4, name: 'foo'}] // Error
};

これもデータの持ち方次第なきもしないでもないですが・・・

あるカテゴリ毎に同じようなレコードがある場合とかに使える感じですかね

ただできることはわかったので後は今後適切な場所で使うことができればOK

Exclude, Extract

定義はこちら(Exclude)

定義はこちら(Extract)

type Hoge = 'a'|'b'|'c';
type Fuga = 'c'|'d'|'e';

type ExcludedHoge = Exclude<Hoge, Fuga>; // a|b|c - c|d|e -> a|b
type ExtractedHoge = Extract<Hoge, Fuga>; // a|b|c & c|d|e -> c

const a: ExcludedHoge = 'c';  // Error

const b: ExtractedHoge = 'a' // Error

こちらは第一引数の型から第二引数の型をどうするか

サンプルコードまんまですね

ExcludeはHogeからFugaを引いたもの

ExtractはHogeとFuga両方にあるもの

Pickなどで特定のキーのデータのみ抽出したいとかそういう場合の特定キーの抽出に使えそう

NonNullable

定義はこちら

type Fuga = 'a'|'b'|null|undefined; // null, undefinedが除外される
const a: NonNullable<Fuga> = 'a';
const b: NonNullable<Fuga> = 'b';
const c: NonNullable<Fuga> = null; // Error
const d: NonNullable<Fuga> = undefined; // Error

与えられたリストからnull,undefiendを除いた型を返す

こちらもサンプル見たまんまですね

Parameters

定義はこちら

const hogeFn = (arg1: number, arg2: string, arg3: boolean): void =>  {};
const fugaFn = (arg1: number): void => {};
type HogeParams = Parameters<typeof hogeFn> // [number, string, boolean]
type FugaParams = Parameters<typeof fugaFn> // [number]

// 関数の引数の型を生成する
// typeof FunctionName で渡すが関数以外のtypeofを渡すとエラーになる
const a: HogeParams = [1, 'a']; // Error
const b: HogeParams = [1, 'a', false]; // ok
const c: FugaParams = [1]; // ok
const d: FugaParams = [1, 'a']; // Error

関数を渡す型関数ですね

関数への引数の型をそのまま型にして返してくれます

関数呼び出し時にこのParameterを使えば関数のIFに変更があってもよしなに気づくことができる感じになりそうですね

ConstructorParameters

定義はこちら

class Animal {
  constructor(hoge: number, fuga: string){
  }
}

type Hogeparams = ConstructorParameters<typeof Animal>;

const a: Hogeparams = [1, 3]// Error
const b: Hogeparams = [1, 'hoge']// ok

Parametersとほとんど同じでこちらはConstructorの引数を型とする関数

ReturnType

定義はこちら

const HogeFn = (args1: number): string => { return 'hoge' }

type Hoge = ReturnType<typeof HogeFn>

const a: Hoge = 1; // Error
const b: Hoge = 'a';

関数の戻り値の型を生成する関数

InstanceType

定義はこちら

やってることはわかるけどどこで使うかピンとこなかったです

下記issueでこういうときに有用だよという議論がされているので一応リンクだけ張っておきます

Need documentation for InstanceType · Issue #25998 · microsoft/TypeScript

github.com

まとめ

これだけでも知っていたら便利に使えるときが来そうに思います

実際こういうのが必要になっていろいろ調べたので必要なはずですw

この「型をゴニョゴニョして新たな型を生成する」系の操作はメタプロっぽくて個人的には面白くなってきています

あんまりやりすぎると書いた人以外わからなくなりそうですが・・・

組み込み型関数くらいであれば同じような記述が減らせてコード書くのも楽になりそうですね