はじめに
TypeScriptには組み込み型関数であるUtility Typesがあります。
その型定義元の中身がどうなっているかを見ていこうと思います。
Partial
/**
* Tの全てのプロパティをオプションにする
*/
type Partial<T> = {
[P in keyof T]?: T[P];
};
引数で渡された型TをMapped Typesを使って、分解し再定義しています。
具体的には、Tのプロパティをinとkeyofを使って、順番にPに代入し、?(オプショナル)をつけてオプションにしています。
そして、その値の型をT[P]で取り出し再定義しています。
Required
/**
* Tの全プロパティを必須にする
*/
type Required<T> = {
[P in keyof T]-?: T[P];
};
こちらも先ほどのPartialと同様にMapped Typesを使って、分解し再定義しています。
違う点として、-?をつけることで?(オプショナル)を外し、プロパティを必須としています。
Readonly
/**
* Tの全てのプロパティを読み取り専用にする
*/
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
こちらもMapped Typesを使って、分解し再定義しています。
再定義する際に、readonlyをつけることでプロパティを読み取り専用としています。
Pick
/**
* Tから、Kで指定されたプロパティのみにする
*/
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};
Pickは引数を2つとり、extendsを使うことで、第二引数には、第一引数で指定した型のプロパティのユニオン型のみに制限しています。
そしてMapped Typesを使い、Kで指定されたプロパティのみを持つ型に再定義しています。
Omit
/**
* Tから、Kで指定されたプロパティ以外にする
*/
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
// ↓↓↓ 展開するとこんな感じになる
type Omit<T, K extends keyof any> = {
[P in keyof T as P extends K ? never: P]: T[P];
};
keyof T as Pで第一引数に指定されたTのプロパティを順番に取り出しPとして定義し、extendsを使い、そのプロパティが第二引数の型Kに割り当て可能な場合はnever、割り当て可能ではない場合はPを返します。
そしてMapped Typesを使い、最終的にはKで指定されたプロパティ以外を持つ型に再定義しています。
Record
/**
* Kをプロパティ、Tを値の型として持つ型にする
*/
type Record<K extends keyof any, T> = {
[P in K]: T;
};
第一引数をextends keyofでユニオン型に制限し、Mapped Typesを使って、分解しTを値の型として持つ型に再定義しています。
Exclude
/**
* Uに割り当て可能な型をTから除外する。
*/
type Exclude<T, U> = T extends U ? never : T;
TがUに割り当て可能な型の場合、never、割り当て可能ではない場合、Tを返します。
ユニオン型から特定のプロパティを除きたい場合に使うことが多いです。
type Union = Exclude<string | number | boolean, boolean>
// => string | number
第一引数にユニオン型を指定した場合、T extends U ? never : TのTに順番にプロパティが入り、プロパティ分繰り返されます。
string extends boolean ? never : string
//=> string
number extends boolean ? never : number
//=> number
boolean extends boolean ? never : boolean
//=> never
// ↓↓↓
string | number
Extract
/**
* Uに割り当て可能な型をTから抽出する。
*/
type Extract<T, U> = T extends U ? T : never;
こちらは先程のExcludeの逆で、TがUに割り当て可能な型の場合、T、割り当て可能ではない場合、neverを返します。
NonNullable
/**
* Tからnullとundefinedを除外する
*/
type NonNullable<T> = T & {};
引数Tと{}に対してインターセクション型を使うことでnullとundefinedを除外しています。
Parameters
/**
* 関数型のパラメータをタプル型にする
*/
type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never;
引数の型Tをextends (...args: any) => anyで関数型に制限します。
T extends (...args: infer P) => any ? P : neverでTが関数型の場合、inferで引数(args)のタプル型Pを取り出し、そのままPを返します。
inferはconditional types(extends)で条件分岐した際に推論される型情報を入れられる型変数です。
また下記のように、同じ型変数に対して複数の推論場所を持つこともできます。
type Unpacked<T> = T extends (infer U)[]
? U
: T extends (...args: any[]) => infer U
? U
: T extends Promise<infer U>
? U
: T;
これは配列、関数、Promiseの中身の方を取り出す型です。
type ArrayArg = type Unpacked<string[]>
// => string
type FunctionArg = type Unpacked<(...args: any) => number>
// => number
type PromiseArg = type Unpacked<Promise<boolean>>
// => boolean
ReturnType
/**
* 関数の戻り値の型を取得する
*/
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;
引数の型Tをextends (...args: any) => anyで関数型に制限します。
T extends (...args: any) => infer R ? R : anyでTが関数型の場合、inferで戻り値の型Pを取り出し、そのPを返します。
ConstructorParameters
/**
* コンストラクタ関数型のパラメータをタプルで取得する
*/
type ConstructorParameters<T extends abstract new (...args: any) => any> = T extends abstract new (...args: infer P) => any ? P : never;
引数Tをextends abstract new (...args: any) => anyでクラスもしくは抽象クラスに制限します。
Tがクラスもしくは抽象クラスの場合、コンストラクタ関数型の引数のタプル型をinfer Pで取得し、そのままPを返しています。
InstanceType
/**
* コンストラクタ関数型の戻り値を取得する
*/
type InstanceType<T extends abstract new (...args: any) => any> = T extends abstract new (...args: any) => infer R ? R : any;
こちらも上記のConstructorParametersと同様に、引数Tをextends abstract new (...args: any) => anyでクラスもしくは抽象クラスに制限します。
Tがクラスもしくは抽象クラスの場合、コンストラクタ関数型の戻り値(クラスのインスタンスの型)をinfer Rで取得し、そのままRを返しています。
間違いなどあればご指摘いただけると幸いです。
参照