Record型やReturnType型などのユーティリティ型をみてみる
ユーティリティ型
TypeScriptには標準で組み込まれている型として、ユーティリティ型があります。
代表的なものにReadonly
型やPartial
型などがあり、下の記事で実装を確認しましたが、ほかのユーティリティ型についてもみてみたいと思います。
Record
型
Mapped Typesを簡単に使えるRecord<K, T>
型は、K
にプロパティキーのユニオン型、T
にプロパティの値の型を指定することで、対応するオブジェクト型を取得できます。
type PropertyKeys = "octopus" | "squid"
type Obj = Record<PropertyKeys, string>
// type Obj = {
// octopus: string;
// squid: string;
// }
Record<K, T>
型の実装は以下のようになっています。
/**
* Construct a type with a set of properties K of type T
*/
type Record<K extends keyof any, T> = {
[P in K]: T;
};
K extends keyof any
から、K
に指定できるのはプロパティキーとして取りうる型、つまりstring | number | sympol
の部分型に限定されています。
type anyKey = keyof any
//type anyKey = string | number | symbol
実装からRecord
型というのは、Mapped Typesという少し複雑な機能をユーティリティ型としてその複雑さを隠蔽し、使いやすくした型であることがわかります。
Promise
を外すAwaited
型
Awaited<T>
型は、非同期処理のawait
に倣って作られた型で、再帰的にPromise
を外した型を返します。
async function asyncFunc() {
setTimeout(()=>{console.log("Loading...")}, 3000)
return "success"
}
async function main(){
const promiseStr = asyncFunc()
//const promiseStr: Promise<string>
console.log(promiseStr)
const str = await asyncFunc()
//const str: string
console.log(str)
}
const promiseStr = asyncFunc()
//const promiseStr: Promise<string>
type Str = Awaited<typeof promiseStr>
//type Str = string
main()
//---Logs---
//Promise: {}
//"success"
//"Loading..."
//"Loading..."
//"Loading..."
async
による非同期関数の返り値はPromise
でラップされた型が返ってきます。await
をつけると非同期処理の完了を待つため、返り値の型はPromise
が外れた状態となりますが、これと同じようにAwaited<T>
型を使うことでPromise
を外すことができます。
Awaited<T>
の実装は以下のようになっています。
/**
* Recursively unwraps the "awaited type" of a type. Non-promise "thenables" should resolve to `never`. This emulates the behavior of `await`.
*/
type Awaited<T> = T extends null | undefined ? T : // special case for `null | undefined` when not in `--strictNullChecks` mode
T extends object & { then(onfulfilled: infer F, ...args: infer _): any; } ? // `await` only unwraps object types with a callable `then`. Non-object types are not unwrapped
F extends ((value: infer V, ...args: infer _) => any) ? // if the argument to `then` is callable, extracts the first argument
Awaited<V> : // recursively unwrap the value
never : // the argument to `then` was not callable
T; // non-object or non-thenable
T extends null | undefined ? T :
の部分により、T
としてnull
かundefined
が渡された場合にはそのままT
が返されます。また、T extends object & ... ? ... : T
の部分から、T
としてオブジェクト以外が渡された場合にもそのまま返されることがわかります。
T
がオブジェクトで、{ then(onfulfilled: infer F, ...args: infer _): any; }
の部分型であるなら、つまりthen
メソッドを持ち、非同期処理成功時のコールバック関数onfullfilled
を持っているならば、その関数をF
として次の条件判定に移ります(then
が呼び出せない場合などはnever
型が返ります)。
F extends ((value: infer V, ...args: infer _) => any) ? Awaited<V> : never
の部分では、onfullfilled
で指定したコールバック関数がvalue
を引数に持っている場合にはそのvalue
の型V
に対してAwaited<V>
を適用します。Awaited
の中にAwaited
型を入れ込むことで再帰的にPromise
を外しているわけですね。
Parameters
型
関数の引数の型、Parameters<T>
は関数の型T
を渡すことで、その関数の引数のタプル型を返します。
function numToString(num: number, msg:string){
console.log(msg)
return String(num)
}
type P = Parameters<typeof numToString>
//type P = [num: number, msg: string]
実装は以下のようになっています。
/**
* Obtain the parameters of a function type in a tuple
*/
type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never;
型引数T
は関数の型であるという制約がT extends (...args: any) => any
の箇所で課されており、T extends (...args: infer P) => any ? P : never;
の箇所で引数の型P
をinfer
によって取得し、そのまま返しています(infer
はConditional Typesの中で使用できるジェネリクス型です)。
ReturnType
型
関数の返り値の型を返す、ReturnType<T>
は関数の型T
から返り値の型を返すユーティリティ型です。
function numToObj(num: number){
return {n:num, msg:"Hello", foo:true}
}
type R = ReturnType<typeof numToObj>
// type R = {
// n: number;
// msg: string;
// foo: boolean;
// }
ReturnType<T>
の実装はParameter<T>
の実装とinfer
が引数型か返り値型かの違いしかなく、とても似たものになっています。
/**
* Obtain the return type of a function type
*/
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;
文字列リテラル型を操作できる組み込み文字列型
文字列リテラル型の大文字・小文字への変換ができる型も存在します。
それが、Uppercase<T>
,Lowercase<T>
,Capitalize<T>
,Uncapitalize<T>
というユーティリティ型です。
type literalStr = "OctoPus" | "sQuID"
type P = Uppercase<literalStr>
//type P = "OCTOPUS" | "SQUID"
type L = Lowercase<literalStr>
//type L = "octopus" | "squid"
type C = Capitalize<literalStr>
//type C = "OctoPus" | "SQuID"
type UC = Uncapitalize<literalStr>
//type UC = "sQuID" | "octoPus"
Uppercase<T>
は全ての文字を大文字に、Lowercase<T>
は全ての文字を小文字にします。
また、Capitalize<T>
,Uncapitalize<T>
はそれぞれ先頭の文字を大文字、小文字にします。
これらのユーティリティ型の実装は以下のようになっています。
/**
* Convert string literal type to uppercase
*/
type Uppercase<S extends string> = intrinsic;
/**
* Convert string literal type to lowercase
*/
type Lowercase<S extends string> = intrinsic;
/**
* Convert first character of string literal type to uppercase
*/
type Capitalize<S extends string> = intrinsic;
/**
* Convert first character of string literal type to lowercase
*/
type Uncapitalize<S extends string> = intrinsic;
intrinsic
は型エイリアスがTypeScriptのコンパイラが提供する実装を参照することを表すもので、どのような実装がされているかはコンパイラを確認する必要があります。
Discussion