TypeScriptの型情報を完全展開!ユーティリティ型IdentifyとNestedIdentifyの活用術
この記事は「BEMALab アドベントカレンダー 2024」22日目の記事です。
はじめに
こんにちは、株式会社メンバーズの樋田です。
普段TypeScriptを書いている中でOmitやPartialなどの組み込み型やライブラリからimportした型を使っている際に、型の情報が最後まで展開されずに困っていました。そんな時Software Design2024年5月号でmizchiさんが型の情報を展開するユーティリティ型の紹介をしていたのでそちらの実装を解説します。また書籍の方法では型エイリアスなどでネストされたオブジェクト型には対応していないので、そちらにも対応したバージョンを自作しました。こちらも使ってみてください!
今回紹介するユーティリティ型の紹介
紹介したいユーティリティ型は次の「Identify型」になります(Software Design 2024年5月号 電子書籍版p161)。
引用元ではプロパティの値の部分が「{ [K in keyof U]: K }」のようにKとなっていますが、本記事では{ [K in keyof U]: U[K] }のようにU[K}としています。
type Identify<T> = T extends infer U ? { [K in keyof U]: U[K] } : never;
また「Identify型」を参考に型エイリアスなどでネストされたオブジェクト型にも対応できるようNestedIdentify型を自作しました!
type NestedIdentify<T> = T extends object
? T extends infer U
? { [K in keyof U]: NestedIdentify<U[K]> }
: never
: T;
課題
例えばmicroCMSのAPIレスポンスの型定義をするとき、microCMS側からインポートした型MicroCMSImageを使うと次の画像のように型の情報が最後まで展開されず不便に感じていました。
「Identify」を使うと
「Identify」の型引数に展開したい型を渡すことで次の画像のように最終的な型を確認することができます。
型エイリアスなどでネストされた型を確認したければNestedIdentifyを使います。
「Identify」の実装の解説
T extends U ? X : Yについて
これはConditional Typesと呼ばれるもので型の条件分岐ができます。JavaScriptの三項演算子のようなものでTがUに割り当て可能であればXに、そうでなければYになります。
https://www.typescriptlang.org/docs/handbook/2/conditional-types.html
extendsの右側にinferを使うことでTを推論することができます。
https://www.typescriptlang.org/docs/handbook/2/conditional-types.html#inferring-within-conditional-types
「{ [K in keyof U]: U[K] }」について
「[K in keyof U]」はMapped Typesと呼ばれるものでkeyof UつまりUというオブジェクトのキーのユニオン型からキーを繰り返します。https://www.typescriptlang.org/docs/handbook/2/mapped-types.html
公式ドキュメントのサンプルコードをお借りして以下に載せます。OptionsFlags型のPropertyにはFeatures型の各プロパティのキーが入るので、FeaturesOptions型のすべてのプロパティの値がbooleanになります。
type OptionsFlags<Type> = {
[Property in keyof Type]: boolean;
};
type Features = {
darkMode: () => void;
newUserProfile: () => void;
};
type FeatureOptions = OptionsFlags<Features>;
// ^type FeatureOptions = {
// darkMode: boolean;
// newUserProfile: boolean;
// }
U[K]について
U[K]はIndexed Access Typesと呼ばれるものでUというオブジェクトの、キーがKの値を取得します。Mapped Typesと組み合わせることで、KにはUが持つ各プロパティのキーが入ってきます。
https://www.typescriptlang.org/docs/handbook/2/indexed-access-types.html
公式ドキュメントのサンプルコードの「Person型」を元にMapped TypesとIndexed Access Typesを組み合わせてNewPerson型を定義すると次のようになります。
type Person = {
age: number;
name: string;
alive: boolean;
};
type NewPerson = { [K in keyof Person]: Person[K] };
// ^type NewPerson = {
// age: number;
// name: string;
// alive: boolean;
// }
Kには最初ageが入るのでage: Person[‘age’]というふうになり、Person[‘age’]はPersonのageプロパティの値(=number)を取り出すのでage: numberとなります。次にKにnameが入り...というふうにaliveまで繰り返されます。
さいごに
本記事では、TypeScriptで複雑なオブジェクト型の情報を展開するためのユーティリティ型を紹介しました。「Identify型」やNestedIdentify型を活用することで、型情報の展開が簡単になり、開発が効率化されると思います!
型情報が展開されず困っている方はぜひご自分の手元で試してみてください!
参考文献
Software Design 2024年5月号, 技術評論社, 2024, 642405
https://gihyo.jp/magazine/SD/archive/2024/202405
この記事を書いた人
関連記事
- Astroをフロントエンドフレームワークとして利用する
Hideki Ikemoto
- React Redux: 毎回新しい参照を返す select...
Daisuke Yamamura