ユーティリティ型は、ある型を別の型に変える、TypeScriptに組み込まれたジェネリックな型です。すべてのフィールドを省略可能にした2つ目の User 型を書く代わりに Partial<User> と書き、3つのフィールドをコピーする代わりに Pick<User, "id" | "name"> と書きます。グローバルなので、インポートは要りません。
User が変われば、派生した4つの型もすべてそれに従います。TypeScriptの標準ライブラリ(lib.es5.d.ts)は22個のユーティリティ型を宣言しています。以下のセクションでは、扱う型の種類ごとにすべてを並べ、詳しいページがあるものにはリンクを付けています。
オブジェクト型: Partial、Required、Readonly、Pick、Omit、Record
| ユーティリティ型 | 働き | 例 |
|---|---|---|
Partial<T> | すべてのプロパティを省略可能にする | 更新のペイロードに Partial<User> |
Required<T> | すべてのプロパティを必須にする(? を取り除く) | デフォルト値を適用したあとの Required<Config> |
Readonly<T> | すべてのプロパティを readonly にする | Readonly<State> |
Pick<T, K> | キー K だけを残す | Pick<User, "id" | "name"> |
Omit<T, K> | キー K を取り除く | Omit<User, "password"> |
Record<K, V> | キーが K、値が V のオブジェクト型 | Record<"en" | "de", string> |
Required<T> は Partial<T> の逆です。Partial のページで両方を扱っていて、このようなスプレッドが通してしまう明示的な undefined についても説明しています。
ユニオン型: Exclude、Extract、NonNullable
| ユーティリティ型 | 働き | 例 |
|---|---|---|
Exclude<U, M> | M に代入できるユニオン型のメンバーを取り除く | Exclude<"a" | "b" | "c", "a"> は "b" | "c" |
Extract<U, M> | M に代入できるユニオン型のメンバーを残す | Extract<string | number, number> は number |
NonNullable<T> | null と undefined を取り除く | NonNullable<string | null> は string |
この3つはオブジェクトではなくユニオン型に対して働きます。オブジェクト型とそのキーの一覧を受け取る Pick や Omit との大きな違いはそこです。
関数とクラスの型: Parameters、ReturnType など
| ユーティリティ型 | 働き | 例 |
|---|---|---|
ReturnType<F> | 関数型の戻り値の型 | ReturnType<typeof createStore> |
Parameters<F> | 引数の型をタプルで | Parameters<typeof fetchPage>[0] |
ConstructorParameters<C> | クラスのコンストラクターの引数をタプルで | ConstructorParameters<typeof Point> |
InstanceType<C> | コンストラクターが作るインスタンスの型 | InstanceType<typeof Point> |
ThisParameterType<F> | 関数の this パラメーターの型 | ThisParameterType<typeof greet> |
OmitThisParameter<F> | this パラメーターを除いた関数型 | greet.bind(obj) の型 |
ThisType<T> | オブジェクトリテラルのメソッドの中の this の型を設定する | ビルダー型のAPIで noImplicitThis と一緒に使う |
NoInfer<T> | 型パラメーターがこの位置から推論されるのを止める | fallback: NoInfer<C> |
これらのユーティリティは型を受け取り、createOrder は値なので、typeof createOrder が必要です。クラスも同じで、typeof Point はコンストラクターの型ですが、型としての普通の Point はすでにインスタンスの型を意味します。
NoInfer は、ジェネリクスがどこから型を得るかを制御します:
NoInfer がなければ、TypeScriptは両方の引数から C を推論して "red" | "green" | "blue" に広げてしまうので、フォールバックの打ち間違いが受け入れられてしまいます。
文字列の型: Uppercase、Lowercase、Capitalize、Uncapitalize
| ユーティリティ型 | 働き | 例 |
|---|---|---|
Uppercase<S> | 文字列リテラル型を大文字にする | Uppercase<"get"> は "GET" |
Lowercase<S> | 小文字にする | Lowercase<"GET"> は "get" |
Capitalize<S> | 先頭の文字を大文字にする | Capitalize<"name"> は "Name" |
Uncapitalize<S> | 先頭の文字を小文字にする | Uncapitalize<"Name"> は "name" |
この4つはTypeScriptで書かれているのではなくコンパイラーに組み込まれていて、イベントハンドラー名を作る `on${Capitalize<E>}` のように、テンプレートリテラル型 の中でいちばん役に立ちます。
Promise: Awaited
| ユーティリティ型 | 働き | 例 |
|---|---|---|
Awaited<T> | await で得られる型。ネストした Promise も展開する | Awaited<Promise<Promise<number>>> は number |
Awaited<ReturnType<typeof fn>> は、async 関数の結果の型を別に宣言せずに名前を付ける標準的な方法です。
ユーティリティ型を組み合わせる
ユーティリティ型は入れ子にできます。覚えておく価値があるほどよく出てくる組み合わせがいくつかあります:
入れ子のユーティリティ型は内側から読みます。Readonly<Pick<Post, "id" | "title">> は、まず2つのプロパティを残し、それから読み取り専用にします。同じ部品で、一部のキーだけを省略可能にする PartialBy ヘルパーも作れます。Partial のページで書き出しています。
ユーティリティ型は実行時に何もしない
ユーティリティ型はすべて、コードのコンパイル時に消去されます。Omit<User, "password"> と型付けされた値でも、元のオブジェクトが password を持っていれば、実行時にはそれを持ったままです:
型が制限するのは、コードが読んでよいものだけです。データからフィールドを取り除くには最後の数行のように分割代入で取り除き、実行時に変更を止めるには Readonly ではなく Object.freeze を使いましょう。組み込みの型は1行のマップ型や条件型なので、同じ道具で自分の型も書けます。
よくある質問
TypeScriptのユーティリティ型とは何ですか?
TypeScriptに同梱されている、ほかの型を変換するジェネリックな型です。Partial<T> はすべてのプロパティを省略可能にし、Pick<T, K> は一部のプロパティを残し、ReturnType<F> は関数の戻り値の型を取得する、といったものです。標準ライブラリで宣言されているので、何もインポートせずに使えます。
ユーティリティ型はインポートする必要がありますか?
ありません。Partial、Omit、Record、ReturnType などは、TypeScriptの組み込みライブラリファイルのグローバルな型です。どこでも Partial<User> と書けて、インポートも npm パッケージも要りません。
TypeScriptにはどんなユーティリティ型が組み込まれていますか?
22個で、すべて lib.es5.d.ts で宣言されています: Partial、Required、Readonly、Pick、Omit、Record、Exclude、Extract、NonNullable、Parameters、ConstructorParameters、ReturnType、InstanceType、ThisParameterType、OmitThisParameter、ThisType、NoInfer、Awaited、Uppercase、Lowercase、Capitalize、Uncapitalize。
ユーティリティ型は実行時にオブジェクトを変えますか?
変えません。型を表すだけで、JavaScriptの出力からは消去されます。Omit<User, "password"> は password プロパティを削除せず、Readonly<T> は何も凍結しません。実際のオブジェクトを変えるには、分割代入の残余パターンや Object.freeze などのコードを書きます。
自分でユーティリティ型を書けますか?
書けます。組み込みのものも普通のTypeScriptで、ほとんどは lib.es5.d.ts にある1行のマップ型か条件型です。type Nullable<T> = { [K in keyof T]: T[K] | null } も同じ方法で書いた自作のユーティリティ型です。