はじめに
私がまとめたTypeScriptに関する記事一覧になります。もし興味がありましたらご覧になってください。
今回は、TypeScriptの高度な型(Utility Types、Conditional Types)についての記事になります。
Utility Types
ユーティリティ型(utility type)は、型から別の型を導き出してくれる型のこと。functionが実行時の世界の関数だとしたら、ユーティリティ型は型の世界の関数というイメージ。TypeScriptで使える既存の型から新しい型を作成するための便利な関数のようなもの。
以下の記事で丁寧にまとめられてたので、引用させていただきました。
Conditional Types
三項演算子のような書き方を用いながら、条件分岐を型の定義に導入できる1つの文法。これによって動的な型を定義することが可能となる。
Conditional Typesを学ぶことによってUtility Typesがどのように定義されているか理解できるようになります。
以下、Conditional Typesで使われているキーワードについて一部解説します。
Mapped Types
Conditional Typesの文法で使用されている以下のような構文のこと。Mappted Typesを使用することにより、既存の型から型を取り出して独自の型を定義する事が可能となる。
type Partial<T> = {
[P in keyof T]?: T[P];
};
-
keyof ・・・Tというオブジェクトの型の全てのプロパティのkeyを文字列リテラル型のunion types(合併型)で引っ張ってきます。
-
in・・・上記のkeyofで取り出された文字列リテラルで構成されたunion型の要素を1つ1つ取り出します。つまり、
P in keyof T
のPにはunion型の要素である文字列リテラルが入っています。
// Mapped Types
type Profile = {
name: string;
age: number;
};
// Utility TypesのPartialを使って型を抽出。
type PartialProfile = Partial<Profile>;
// Conditional Typesを使って独自の型Optionalを定義。
// 中身はPartialと同じ。
type Optional<T> = { [P in keyof T]?: T[P] };
type OptionalProfile = Optional<Profile>;
extends
TypeScriptで使えるキーワードの1つ。
T extends U
という構文があった場合、TはUの部分型(上位互換)であるという意味になる。
// Compatibilityはtrue
type Compatibility = string extends string | number | boolean ? true : false;
// NonCompatibilityはfalse
type NonCompatibility = string extends number | boolean ? true : false;
-
string型はstring | number | booleanに対して互換性があるので、Compatibilityにはtrueが渡ってきます。
-
string型は number | booleanに対して互換性がないので、NonCompatibilityにはfalseが渡ってきます。
Distributive Conditional Types
T extends U ? X : Y
というConditional TypesのTにユニオン型が渡って来た時の構文のこと。
T extends U ? X : YのTの型引数にA | B | Cが渡された場合T extends U ? X : Yは(A extends U ? X : Y) | (B extends U ? X : Y) | (C extends U ? X : Y)のように展開されます。
type T = A | B | C;
type Exclude<T, U> = T extends U ? never : T;
// 上記の右辺の中身を分解すると以下と同義になる。
(A extends U ? never : A) | (B extends U ? never : B) | (C extends U ? never : C)
infer
inferキーワードは、TypeScriptのconditional typesで使用される特殊なキーワード。
型の一部を抽出し、それを新しい型パラメータとして利用。これにより、型の一部を柔軟に推論して再利用することが可能となる。
Conditional Typesでは無数の条件式を書くことができて、その条件式の中で型を拾い上げたい場面があります。戻り値の型をなんとかして拾い上げたいと思ったときに、どんな型かわからないけどピックアップしたい、そういう制御ができるのがinfer R
です。この場合戻り値の型がRとして扱われます。
inferの意味は直訳すると察知する、推論するという意味です。つまり型として推論した結果をRに代入するという意味になります。
function add(a: number, b: number) {
return a + b
}
type ReturnTypeFromAdd = ReturnType<typeof add>; // number型
type MyReturnType<T extends (...args: any) => any> = T extends (
...args: any
) => infer R
? R
: any;
type MyReturnTypeFromAdd = MyReturnType<typeof add>; // number型
- 上記のReturnTypeFromAdd関数はUtility Typesの1つである
ReturnType
を使い、関数の戻り値の型(number)を抽出する関数です。 - MyReturnTypeは
infer
とextends
を使って関数の戻り値の型を抽出するConditional Types。ReturnType
のConditional Typesの構造と全く同じです。
まとめ
-
Utility Types
はTypeScriptのコードで利用できる型変換を容易に行える便利な関数の集まりです。 -
Conditional Types
はUtility Types
の裏側で動いている仕組みで、多くのUtility Types
はConditional Types
をベースにして作られています。
おわりに
最後まで記事をご覧いただきありがとうございました。
間違いなどありましたらご指摘いただけると幸いです。
参考