【TypeScript】ユーティリティ型を自作して、型の理解を深める
どうもフロントエンドエンジニアのoreoです。
みなさん、type-challengesをご存じでしょうか?type-challengesとは、TypeScriptの型問題が記載されているリポジトリです。easy、medium、hard、extremeの4つのレベルに別れており、全部で140問ほど、出題されています。
今回は、type-challengesを参考にしながら、TypeScriptの代表的なユーティリティ型を自分で定義し、型の理解を深めたいと思います。
この記事では、ユーティリティ型のPartial
、Required
、Readonly
、Pick
、Omit
、Exclude
を扱います!
1 Partial<T>
1-1 書き換え
Partial<T>
は、T
の全てのプロパティをオプショナルにするユーティリティ型です。
type CustomerData = {
name: string;
age: number;
gender?: string;
};
type PartialCustomerData = Partial<CustomerData>;
PartialCustomerData
は👇のようになります。
type PartialCustomerData = {
name?: string;
age?: number;
gender?: string;
};
Partial<T>
は、👇のMyPartial<T>
のように書き換えることができます。
type MyPartial<T> = {
[P in keyof T]?: T[P];
};
type PartialCustomerData = MyPartial<CustomerData>;
1-2 ポイント
ここでのポイントはHomomorphic mapped typeです。これは、{ [P in keyof T] : U }
のような形をとりオブジェクト型T
のプロパティP
に対して、型U
を持つようなオブジェクト型を生成します。
Homomorphic mapped typeに関しては、👇の記事をご覧ください。
2 Required<T>
2-1 書き換え
Required<T>
は、T
の全てのプロパティを必須にするユーティリティ型です。
type CustomerData = {
name: string;
age: number;
gender?: string;
};
type RequiredCustomerData = Required<CustomerData>;
RequiredCustomerData
は、👇のようになります。
type RequiredCustomerData = {
name: string;
age: number;
gender: string;
};
Required<T>
は、👇のMyRequired<T>
のように書き換えることができます。
type MyRequired<T> = {
[P in keyof T]-?: T[P];
};
type RequiredCustomerData = MyRequired<CustomerData>;
2-2 ポイント
ポイントは、1-2のHomomorphic mapped typeに加えて、-?
です。プロパティに-?
をつけると、プロパティが必須になります。
3 Readonly<T>
3-1 書き換え
Readonly<T>
は、T
の全てのプロパティを読み取り専用にするユーティリティ型です。
type CustomerData = {
name: string;
age: number;
gender?: string;
};
type ReadonlyCustomerData = Readonly<CustomerData>;
ReadonlyCustomerData
は、👇のようになります。
type ReadonlyCustomerData = {
readonly name: string;
readonly age: number;
readonly gender?: string;
}
Readonly<T>
は、👇のMyReadonly<T>
のように書き換えることができます。
type MyReadonly<T> = {
readonly [P in keyof T] :T[P];
}
type ReadonlyCustomerData = MyReadonly<CustomerData>;
3-2 ポイント
ポイントは、1-2のHomomorphic mapped typeに加えて、readonly
を付けてあげると、それぞれのプロパティを読み取り専用にしてくれます。
ちなみに、readonly
を削除したい場合は、プロパティの前に-readonly
を付ければ、削除することが可能です。
4 Pick<T,Keys>
4-1 書き換え
Pick<T,Keys>
は、型T
からKeys
で指定したプロパティを含むオブジェクト型を返すユーティリティ型です。
type CustomerData = {
name: string;
age: number;
gender?: string;
};
type CustomerNameLists = Pick<CustomerData,"name" | "age">
CustomerNameLists
は、👇のようになります。
type CustomerNameLists = {
name: string;
age: number;
}
Pick<T,Keys>
は、👇のMyPick<T, Keys extends keyof T>
のように書き換えることができます。
type MyPick<T, Keys extends keyof T> = {
[key in Keys] : T[key]
}
type CustomerNameLists = MyPick<CustomerData,"name" | "age">
4-2 ポイント
ポイントは、Keys extends keyof T
とMapped typeです。extends keyof
で、Keys
はT
のプロパティのユニオン型として制限をかけています。
また、Mapped typeで、ユニオン型Keys
の構成要素key
をプロパティとし、それに対して、オブジェクト型T
のプロパティkey
が持つ型を設定しています。
Mapped typeに関しては、👇の記事をご覧ください。
5 Omit<T,keys>
5-1 書き換え
Omit<T,Keys>
は、T
からユニオン型Keys
で指定したプロパティを除くオブジェクト型を返すユーティリティ型です。
type CustomerData = {
name: string;
age: number;
gender?: string;
};
type CustomerNameLists = Omit<CustomerData,"age" | "gender">
CustomerNameLists
は、👇のようになります。
type CustomerNameLists = {
name: string;
}
Omit<T,Keys>
は、👇のMyOmit<T, Keys extends keyof T>
のように書き換えることができます。
type MyOmit<T, Keys extends keyof T> = {
[Key in keyof T as Key extends Keys ? never : Key]: T[Key]
}
type CustomerNameLists = MyOmit<CustomerData,"age" | "gender">
5-2 ポイント
ポイントは、as Key extends Keys ? never : Key
の部分です。
ここでは、Conditional Typeを使って、型の条件分岐を行っており、T
のプロパティであるkey
が、Keys
のサブタイプであればnever
型を返し、サブタイプでなければkey
を返します。プロパティに対して、as
でnever
をアサーションすると、プロパティを削除することができます。
Conditional Typeについては、👇の記事をご覧ください。
6 Exclude<T,U>
6-1 書き換え
Exclude<T,U>
は、ユニオン型T
から、U
で指定した型を除いたユニオン型を返すユーティリティ型です。
type JobGrade = "S" | "A" | "B" | "C" | "D";
type SuperJobGrade = Exclude<JobGrade, "B" | "C" | "D">;
SuperJobGrade
は、👇のようになります。
type SuperJobGrade = "S" | "A"
Exclude<T,U>
は、👇のMyExclude<T, U>
のように書き換えることができます。
type MyExclude<T, U > = T extends U ? never : T
type SuperJobGrade = MyExclude<JobGrade, "B" | "C" | "D">;
6-2 ポイント
ポイントは、Conditional TypeであるT extends U ? never : T
でのUnion distribution(ユニオンの分配)です。Conditional Typeでは、T
がユニオン型の場合、T
のそれぞれの要素に対して、Conditional Typeが適用されます。そのためU
のサブタイプである、T
の要素には、never
が返され、型の削除が可能になります。
Conditional Typeの分配法則については、👇の記事をご覧ください。
7 最後に
実際自分で手を動かすと理解が格段に深まりますね。type-challengesに、たくさん問題があるので興味のある方は是非トライしてみてください!私はまたmediumの途中ですが、隙間時間に進めたいです!
8 参考
Discussion