テンプレートリテラル型

テンプレートリテラル型は文字列リテラル型をベースにしており、ユニオンを介して多くの文字列に展開する機能があります。

JavaScriptのテンプレートリテラル文字列と同じ構文ですが、型位置で使用されます。具体的なリテラル型で使用する場合、テンプレートリテラルは内容を連結して新しい文字列リテラル型を生成します。

ts
type World = "world";
 
type Greeting = `hello ${World}`;
type Greeting = "hello world"
Try

補間位置にユニオンが使用されている場合、型は各ユニオンメンバーで表すことができるすべての可能な文字列リテラルの集合になります。

ts
type EmailLocaleIDs = "welcome_email" | "email_heading";
type FooterLocaleIDs = "footer_title" | "footer_sendoff";
 
type AllLocaleIDs = `${EmailLocaleIDs | FooterLocaleIDs}_id`;
type AllLocaleIDs = "welcome_email_id" | "email_heading_id" | "footer_title_id" | "footer_sendoff_id"
Try

テンプレートリテラルの各補間位置について、ユニオンはクロス乗算されます。

ts
type AllLocaleIDs = `${EmailLocaleIDs | FooterLocaleIDs}_id`;
type Lang = "en" | "ja" | "pt";
 
type LocaleMessageIDs = `${Lang}_${AllLocaleIDs}`;
type LocaleMessageIDs = "en_welcome_email_id" | "en_email_heading_id" | "en_footer_title_id" | "en_footer_sendoff_id" | "ja_welcome_email_id" | "ja_email_heading_id" | "ja_footer_title_id" | "ja_footer_sendoff_id" | "pt_welcome_email_id" | "pt_email_heading_id" | "pt_footer_title_id" | "pt_footer_sendoff_id"
Try

一般的に、大規模な文字列ユニオンには事前に生成することをお勧めしますが、小規模なケースではこれは便利です。

型内の文字列ユニオン

テンプレートリテラルの威力は、型内の情報に基づいて新しい文字列を定義する場合に発揮されます。

関数(`makeWatchedObject`)が渡されたオブジェクトに`on()`という新しい関数を追加するケースを考えてみましょう。JavaScriptでは、その呼び出しは`makeWatchedObject(baseObject)`のように見えるかもしれません。ベースオブジェクトは次のようになっていると想像できます。

ts
const passedObject = {
firstName: "Saoirse",
lastName: "Ronan",
age: 26,
};
Try

`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)`となる可能性があります。しかし、上記の記述で、コードに記述したい重要な型制約を特定しました。テンプレートリテラル型を使用すると、これらの制約をコードに取り込むことができます。

ts
const person = makeWatchedObject({
firstName: "Saoirse",
lastName: "Ronan",
age: 26,
});
 
// makeWatchedObject has added `on` to the anonymous Object
 
person.on("firstNameChanged", (newValue) => {
console.log(`firstName was changed to ${newValue}!`);
});
Try

`on`は`firstName`だけでなく、`firstNameChanged`イベントをリスンしていることに注意してください。監視対象オブジェクトの属性名のユニオンに「Changed」を追加することで、有効なイベント名のセットが制約されるようにすれば、`on()`の単純な仕様をより堅牢にすることができます。JavaScriptでそのような計算を行うこと(つまり`Object.keys(passedObject).map(x => `${x}Changed`)`)には満足していますが、型システム内のテンプレートリテラルは文字列操作に同様のアプローチを提供します。

ts
type PropEventSource<Type> = {
on(eventName: `${string & keyof Type}Changed`, callback: (newValue: any) => void): void;
};
 
/// Create a "watched object" with an `on` method
/// so that you can watch for changes to properties.
declare function makeWatchedObject<Type>(obj: Type): Type & PropEventSource<Type>;
Try

これにより、間違ったプロパティが与えられたときにエラーが発生するものを構築できます。

ts
const person = makeWatchedObject({
firstName: "Saoirse",
lastName: "Ronan",
age: 26
});
 
person.on("firstNameChanged", () => {});
 
// Prevent easy human error (using the key instead of the event name)
person.on("firstName", () => {});
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"'.
 
// It's typo-resistant
person.on("frstNameChanged", () => {});
Argument 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"'.
Try

テンプレートリテラルによる推論

元の渡されたオブジェクトで提供されたすべての情報から利益を得ていないことに注意してください。`firstName`(つまり`firstNameChanged`イベント)の変更が与えられると、コールバックが`string`型の引数を受け取ると期待する必要があります。同様に、`age`の変更に対するコールバックは、`number`型の引数を受け取る必要があります。`callback`の引数の型付けには`any`をナイーブに使用しています。繰り返しますが、テンプレートリテラル型を使用すると、属性のデータ型がその属性のコールバックの最初の引数の型と同じであることを保証できます。

これを可能にするための重要な洞察は次のとおりです。

  1. 最初の引数で使用されるリテラルは、リテラル型としてキャプチャされます。
  2. そのリテラル型は、ジェネリックスの有効な属性のユニオンにあるかどうかを検証できます。
  3. 検証された属性の型は、インデックスアクセスを使用してジェネリックスの構造から検索できます。
  4. この型情報は、コールバック関数の引数が同じ型であることを保証するために使用できます。
ts
type PropEventSource<Type> = {
on<Key extends string & keyof Type>
(eventName: `${Key}Changed`, callback: (newValue: Type[Key]) => void): void;
};
 
declare function makeWatchedObject<Type>(obj: Type): Type & PropEventSource<Type>;
 
const person = makeWatchedObject({
firstName: "Saoirse",
lastName: "Ronan",
age: 26
});
 
person.on("firstNameChanged", newName => {
(parameter) newName: string
console.log(`new name is ${newName.toUpperCase()}`);
});
 
person.on("ageChanged", newAge => {
(parameter) newAge: number
if (newAge < 0) {
console.warn("warning! negative age");
}
})
Try

ここでは、`on`をジェネリックメソッドにしました。

ユーザーが`firstNameChanged`という文字列で呼び出すと、TypeScriptは`Key`に適切な型を推論しようとします。そのため、`Key`を`"Changed"`の手前のコンテンツと照合し、`"firstName"`という文字列を推論します。TypeScriptがそれを理解すると、`on`メソッドは元のオブジェクトの`firstName`の型(この場合は`string`)を取得できます。同様に、`ageChanged`で呼び出されると、TypeScriptは`age`プロパティの型である`number`を見つけます。

推論はさまざまな方法で組み合わせることができ、多くの場合、文字列を分解し、さまざまな方法で再構築するために使用されます。

組み込み文字列操作型

文字列操作を支援するために、TypeScriptには文字列操作で使用できる一連の型が含まれています。これらの型はパフォーマンスのためにコンパイラに組み込まれており、TypeScriptに含まれる.d.tsファイルには存在しません。

Uppercase<StringType>

文字列内の各文字を大文字に変換します。

ts
type Greeting = "Hello, world"
type ShoutyGreeting = Uppercase<Greeting>
type ShoutyGreeting = "HELLO, WORLD"
 
type ASCIICacheKey<Str extends string> = `ID-${Uppercase<Str>}`
type MainID = ASCIICacheKey<"my_app">
type MainID = "ID-MY_APP"
Try

Lowercase<StringType>

文字列内の各文字を小文字に変換します。

ts
type Greeting = "Hello, world"
type QuietGreeting = Lowercase<Greeting>
type QuietGreeting = "hello, world"
 
type ASCIICacheKey<Str extends string> = `id-${Lowercase<Str>}`
type MainID = ASCIICacheKey<"MY_APP">
type MainID = "id-my_app"
Try

Capitalize<StringType>

文字列の最初の文字を大文字に変換します。

ts
type LowercaseGreeting = "hello, world";
type Greeting = Capitalize<LowercaseGreeting>;
type Greeting = "Hello, world"
Try

Uncapitalize<StringType>

文字列の最初の文字を小文字に変換します。

ts
type UppercaseGreeting = "HELLO WORLD";
type UncomfortableGreeting = Uncapitalize<UppercaseGreeting>;
type UncomfortableGreeting = "hELLO WORLD"
Try
組み込み文字列操作型の技術的な詳細

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;
}

TypeScriptドキュメントはオープンソースプロジェクトです。プルリクエストを送信して、これらのページの改善にご協力ください by sending a Pull Request

このページへの貢献者
SHSteven Harms (6)
OTOrta Therox (4)
SGHSteven G. Harms (3)
SPSeol Park (1)
PPenchy (1)
6+

最終更新日: 2024年3月21日