テンプレートリテラル型は文字列リテラル型をベースにしており、ユニオンを介して多くの文字列に展開する機能があります。
JavaScriptのテンプレートリテラル文字列と同じ構文ですが、型位置で使用されます。具体的なリテラル型で使用する場合、テンプレートリテラルは内容を連結して新しい文字列リテラル型を生成します。
tsTry
typeWorld = "world";typeGreeting = `hello ${World }`;
補間位置にユニオンが使用されている場合、型は各ユニオンメンバーで表すことができるすべての可能な文字列リテラルの集合になります。
tsTry
typeEmailLocaleIDs = "welcome_email" | "email_heading";typeFooterLocaleIDs = "footer_title" | "footer_sendoff";typeAllLocaleIDs = `${EmailLocaleIDs |FooterLocaleIDs }_id`;
テンプレートリテラルの各補間位置について、ユニオンはクロス乗算されます。
tsTry
typeAllLocaleIDs = `${EmailLocaleIDs |FooterLocaleIDs }_id`;typeLang = "en" | "ja" | "pt";typeLocaleMessageIDs = `${Lang }_${AllLocaleIDs }`;
一般的に、大規模な文字列ユニオンには事前に生成することをお勧めしますが、小規模なケースではこれは便利です。
型内の文字列ユニオン
テンプレートリテラルの威力は、型内の情報に基づいて新しい文字列を定義する場合に発揮されます。
関数(`makeWatchedObject`)が渡されたオブジェクトに`on()`という新しい関数を追加するケースを考えてみましょう。JavaScriptでは、その呼び出しは`makeWatchedObject(baseObject)`のように見えるかもしれません。ベースオブジェクトは次のようになっていると想像できます。
tsTry
constpassedObject = {firstName : "Saoirse",lastName : "Ronan",age : 26,};
`on`関数は、ベースオブジェクトに追加され、2つの引数、`eventName`(`string`)と`callback`(`function`)を期待します。
`eventName`は`attributeInThePassedObject + "Changed"`の形式でなければなりません。したがって、ベースオブジェクトの属性`firstName`から派生した`firstNameChanged`です。
`callback`関数は、呼び出されたとき
- `attributeInThePassedObject`という名前と関連付けられた型の値が渡される必要があります。したがって、`firstName`は`string`として型指定されているため、`firstNameChanged`イベントのコールバックは、呼び出し時に`string`が渡されることを期待します。同様に、`age`に関連付けられたイベントは、`number`型の引数で呼び出されることを期待する必要があります。
- `void`戻り値型を持つ必要があります(デモの簡略化のため)。
`on()`の単純な関数シグネチャは、`on(eventName: string, callback: (newValue: any) => void)`となる可能性があります。しかし、上記の記述で、コードに記述したい重要な型制約を特定しました。テンプレートリテラル型を使用すると、これらの制約をコードに取り込むことができます。
tsTry
constperson =makeWatchedObject ({firstName : "Saoirse",lastName : "Ronan",age : 26,});// makeWatchedObject has added `on` to the anonymous Objectperson .on ("firstNameChanged", (newValue ) => {console .log (`firstName was changed to ${newValue }!`);});
`on`は`firstName`だけでなく、`firstNameChanged`イベントをリスンしていることに注意してください。監視対象オブジェクトの属性名のユニオンに「Changed」を追加することで、有効なイベント名のセットが制約されるようにすれば、`on()`の単純な仕様をより堅牢にすることができます。JavaScriptでそのような計算を行うこと(つまり`Object.keys(passedObject).map(x => `${x}Changed`)`)には満足していますが、型システム内のテンプレートリテラルは文字列操作に同様のアプローチを提供します。
tsTry
typePropEventSource <Type > = {on (eventName : `${string & keyofType }Changed`,callback : (newValue : any) => void): void;};/// Create a "watched object" with an `on` method/// so that you can watch for changes to properties.declare functionmakeWatchedObject <Type >(obj :Type ):Type &PropEventSource <Type >;
これにより、間違ったプロパティが与えられたときにエラーが発生するものを構築できます。
tsTry
constperson =makeWatchedObject ({firstName : "Saoirse",lastName : "Ronan",age : 26});person .on ("firstNameChanged", () => {});// Prevent easy human error (using the key instead of the event name)Argument of type '"firstName"' is not assignable to parameter of type '"firstNameChanged" | "lastNameChanged" | "ageChanged"'.2345Argument of type '"firstName"' is not assignable to parameter of type '"firstNameChanged" | "lastNameChanged" | "ageChanged"'.person .on ("firstName" , () => {});// It's typo-resistantArgument of type '"frstNameChanged"' is not assignable to parameter of type '"firstNameChanged" | "lastNameChanged" | "ageChanged"'.2345Argument of type '"frstNameChanged"' is not assignable to parameter of type '"firstNameChanged" | "lastNameChanged" | "ageChanged"'.person .on ("frstNameChanged" , () => {});
テンプレートリテラルによる推論
元の渡されたオブジェクトで提供されたすべての情報から利益を得ていないことに注意してください。`firstName`(つまり`firstNameChanged`イベント)の変更が与えられると、コールバックが`string`型の引数を受け取ると期待する必要があります。同様に、`age`の変更に対するコールバックは、`number`型の引数を受け取る必要があります。`callback`の引数の型付けには`any`をナイーブに使用しています。繰り返しますが、テンプレートリテラル型を使用すると、属性のデータ型がその属性のコールバックの最初の引数の型と同じであることを保証できます。
これを可能にするための重要な洞察は次のとおりです。
- 最初の引数で使用されるリテラルは、リテラル型としてキャプチャされます。
- そのリテラル型は、ジェネリックスの有効な属性のユニオンにあるかどうかを検証できます。
- 検証された属性の型は、インデックスアクセスを使用してジェネリックスの構造から検索できます。
- この型情報は、コールバック関数の引数が同じ型であることを保証するために使用できます。
tsTry
typePropEventSource <Type > = {on <Key extends string & keyofType >(eventName : `${Key }Changed`,callback : (newValue :Type [Key ]) => void): void;};declare functionmakeWatchedObject <Type >(obj :Type ):Type &PropEventSource <Type >;constperson =makeWatchedObject ({firstName : "Saoirse",lastName : "Ronan",age : 26});person .on ("firstNameChanged",newName => {console .log (`new name is ${newName .toUpperCase ()}`);});person .on ("ageChanged",newAge => {if (newAge < 0) {console .warn ("warning! negative age");}})
ここでは、`on`をジェネリックメソッドにしました。
ユーザーが`firstNameChanged`という文字列で呼び出すと、TypeScriptは`Key`に適切な型を推論しようとします。そのため、`Key`を`"Changed"`の手前のコンテンツと照合し、`"firstName"`という文字列を推論します。TypeScriptがそれを理解すると、`on`メソッドは元のオブジェクトの`firstName`の型(この場合は`string`)を取得できます。同様に、`ageChanged`で呼び出されると、TypeScriptは`age`プロパティの型である`number`を見つけます。
推論はさまざまな方法で組み合わせることができ、多くの場合、文字列を分解し、さまざまな方法で再構築するために使用されます。
組み込み文字列操作型
文字列操作を支援するために、TypeScriptには文字列操作で使用できる一連の型が含まれています。これらの型はパフォーマンスのためにコンパイラに組み込まれており、TypeScriptに含まれる.d.ts
ファイルには存在しません。
Uppercase<StringType>
文字列内の各文字を大文字に変換します。
例
tsTry
typeGreeting = "Hello, world"typeShoutyGreeting =Uppercase <Greeting >typeASCIICacheKey <Str extends string> = `ID-${Uppercase <Str >}`typeMainID =ASCIICacheKey <"my_app">
Lowercase<StringType>
文字列内の各文字を小文字に変換します。
例
tsTry
typeGreeting = "Hello, world"typeQuietGreeting =Lowercase <Greeting >typeASCIICacheKey <Str extends string> = `id-${Lowercase <Str >}`typeMainID =ASCIICacheKey <"MY_APP">
Capitalize<StringType>
文字列の最初の文字を大文字に変換します。
例
tsTry
typeLowercaseGreeting = "hello, world";typeGreeting =Capitalize <LowercaseGreeting >;
Uncapitalize<StringType>
文字列の最初の文字を小文字に変換します。
例
tsTry
typeUppercaseGreeting = "HELLO WORLD";typeUncomfortableGreeting =Uncapitalize <UppercaseGreeting >;
組み込み文字列操作型の技術的な詳細
TypeScript 4.1時点のこれらの組み込み関数のコードは、操作にJavaScriptの文字列ランタイム関数を使用しており、ロケールを認識しません。
function applyStringMapping(symbol: Symbol, str: string) {
switch (intrinsicTypeKinds.get(symbol.escapedName as string)) {
case IntrinsicTypeKind.Uppercase: return str.toUpperCase();
case IntrinsicTypeKind.Lowercase: return str.toLowerCase();
case IntrinsicTypeKind.Capitalize: return str.charAt(0).toUpperCase() + str.slice(1);
case IntrinsicTypeKind.Uncapitalize: return str.charAt(0).toLowerCase() + str.slice(1);
}
return str;
}