項目57 再帰的なジェネリック型は末尾再帰にする
再帰的な型エイリアスは可能な限り末尾再帰にする
特に、文字列リテラル型を一文字ずつ処理するジェネリックを作る際は気をつける
code:ts
type GetChars<S extends string> =
S extends ${infer FirstChar}${infer RestOfString}
? FirstChar | GetChars<RestOfString>
: never;
type Long = GetChars<"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWX">;
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Type instantiation is excessively deep and possibly infinite.
// 型のインスタンス化は非常に深く、無限である可能性があります。
再帰的な型エイリアスは、多くの場合アキュムレーターを使うようにすることで、末尾再帰にできる
末尾再帰でない場合
code:ts
type ToSnake<T extends string> =
string extends T
? string // ToSnake<string>はstringにしたい
: T extends ${infer First}${infer Rest}
? (First extends Uppercase<First> // Firstは大文字?
? _${Lowercase<First>}${ToSnake<Rest>} // たとえば"B"は"_b"に変換
: ${First}${ToSnake<Rest>})
: T
type Long = ToSnake<'reallyDescriptiveNamePropThatsALittleTooLoquacious'>;
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Type instantiation is excessively deep and possibly infinite.
// 型のインスタンス化は非常に深く、無限である可能性があります。
アキュムレーターを使って末尾再帰にした場合
code:ts
type ToSnake<T extends string, Acc extends string = ""> =
string extends T
? string // ToSnake<string>はstringにしたい
: T extends ${infer First}${infer Rest}
? ToSnake<
Rest,
First extends Uppercase<First>
? ${Acc}_${Lowercase<First>}
: ${Acc}${First}
: Acc;
type Long = ToSnake<'reallyDescriptiveNamePropThatsALittleTooLoquacious'>;
// ^? type Long = "really_descriptive_name_prop_thats_a_little_too_loquacious"