はじめに
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
を返しています。
間違いなどあればご指摘いただけると幸いです。
参照