LoginSignup
15
15

More than 1 year has passed since last update.

【TypeScript】Utility Typesの型定義元を見て理解を深める

Last updated at Posted at 2023-01-01

はじめに

TypeScriptには組み込み型関数であるUtility Typesがあります。

その型定義元の中身がどうなっているかを見ていこうと思います。

Partial

/**
 * Tの全てのプロパティをオプションにする
 */
type Partial<T> = {
    [P in keyof T]?: T[P];
};

引数で渡された型TMapped Typesを使って、分解し再定義しています。

具体的には、Tのプロパティをinkeyofを使って、順番に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;

TUに割り当て可能な型の場合、never、割り当て可能ではない場合、Tを返します。

ユニオン型から特定のプロパティを除きたい場合に使うことが多いです。

type Union = Exclude<string | number | boolean, boolean>
// => string | number

第一引数にユニオン型を指定した場合、T extends U ? never : TTに順番にプロパティが入り、プロパティ分繰り返されます。

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の逆で、TUに割り当て可能な型の場合、T、割り当て可能ではない場合、neverを返します。

NonNullable

/**
 * Tからnullとundefinedを除外する
 */
type NonNullable<T> = T & {};

引数T{}に対してインターセクション型を使うことでnullundefinedを除外しています。

Parameters

/**
 * 関数型のパラメータをタプル型にする
 */
type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never;

引数の型Textends (...args: any) => anyで関数型に制限します。

T extends (...args: infer P) => any ? P : neverTが関数型の場合、inferで引数(args)のタプル型Pを取り出し、そのままPを返します。

inferconditional 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;

引数の型Textends (...args: any) => anyで関数型に制限します。

T extends (...args: any) => infer R ? R : anyTが関数型の場合、inferで戻り値の型Pを取り出し、そのPを返します。

ConstructorParameters

/**
 * コンストラクタ関数型のパラメータをタプルで取得する
 */
type ConstructorParameters<T extends abstract new (...args: any) => any> = T extends abstract new (...args: infer P) => any ? P : never;

引数Textends 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と同様に、引数Textends abstract new (...args: any) => anyでクラスもしくは抽象クラスに制限します。

Tがクラスもしくは抽象クラスの場合、コンストラクタ関数型の戻り値(クラスのインスタンスの型)をinfer Rで取得し、そのままRを返しています。

間違いなどあればご指摘いただけると幸いです。

参照

15
15
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
15
15