TypeScriptの型でString.splitっぽいことをする
★Template String Typesは現在Pull Request段階です。 microsoft/TypeScript#40336
★この機能は TypeScript Playground にて動作を試せます。 Playground v4.1.0-insiders.20200831
Template String Typesとは
TypeScript 4.1.0 向けのPRで Template String Types なる機能が実装されました。
簡単に言うと、文字列から新たな型を生成したり、文字列から文字列を参照したりできるようにする機能です。
早い話が、型レベルでテンプレートリテラルが使えるようになります(雑)
たとえば、これまで下記のような関数は string 型としか推論されませんでした。
code:ts
function date(year: string, month: string, date: string) {
return ${year}/${month}/${date};
}
date('2020', '10', '10');
// js: 2020/10/10
// type: string
Template String Typeでは、適切な型定義をあてがうことで、JSと同じ用に文字列結合を型で表せるようになりました。
code:ts
function date<
Y extends string,
M extends string,
D extends string
(year: Y, month: M, date: D): ${Y}/${M}/${D} {
// 文字列の結合自体は正しく推論されない
return ${year}/${date}/${month} as ${Y}/${M}/${D};
}
date('2020', '10', '10');
// js: 2020/10/10
// type: 2020/10/10
Template String Typesで型レベルで String.split する
ソースコード
Template String Typesで型安全なstring.splitを書いてみます。
Template String Typesでは文字列結合のみでなく、参照時にもテンプレートリテラルライクな記法で文字列の一部を参照できます。
code:string-split.ts
type Split<T extends string, S extends string> =
string extends T ? string[]
: T extends ${infer V}${S}${infer Rest} ? V, ...Split<Rest, S>
: T;
function split<V extends string, S extends string>(input: V, seperator: S): Split<V, S> {
return input.split(seperator) as Split<V, S>;
}
Split型の解説
メインは Split 型なので、関数の方は無視します。
2行目でTが String Listeral Types ではなく string型 の場合のハンドリングを行っています。
これはTemplate String Types で検証する対象が string 型の場合は常にTrueと判定され、3行目が無限ループするためです。
2行目を省略した下記のコードでは [V, ...Split<Rest, S>] の部分が Type instantiation is excessively deep and possibly infinite. としてエラーになります。
code:loop-split.ts
type Split<T extends string, S extends string> =
T extends ${infer V}${S}${infer Rest} ? V, ...Split<Rest, S>
: T;
type A = Split<string, '/'>; // string, string, string ...
3行目で文字列の分割を表現しています。
T extends \`${infer V}${S}${infer Rest}\` で S (区切り文字)の前のチャンクと以降のチャンクに分けています。
この条件にマッチする(以降にチャンクが存在する)ときは再帰的に Split 型を充て、一致しない場合は分割できないので要素1の配列を返しています。
code:ts
: T extends ${infer V}${S}${infer Rest} ? V, ...Split<Rest, S>
: T;
実行結果について
分割結果がJavaScriptと同じレベルで推論されるため、「splitしたものを分割代入する」ケースなどで実行前にバグが発生しうるか判断できます。
下記の例ではYYYY/MM/DDの形で受け取った文字列を/で分割する例です。
2番目のコードでは入力値に/が不足しているためdateがundefinedになりますが、それを型レベルで推論してエラーにしてくれます。
code:ts
const year, month, date = split('2020/10/10', '/');
// '2020', '10', '10'
const year, month, date = split('2020/1010', '/');
// Tuple type '"2020", "1010"' of length '2' has no element at index '2'.
#TypeScript #TemplateStringTypes
Created by tamuraryoya