模版字面量类型

模版字面量类型以字符串字面量类型为基础,且可以展开为多个字符串类型的联合类型。

其语法与 是一致的,但是是用在类型的位置上。 当与某个具体的字面量类型一起使用时,模版字面量会将文本连接从而生成一个新的字符串字面量类型。

如果在替换字符串的位置是联合类型,那么结果类型是由每个联合类型成员构成的字符串字面量的集合:

  1. type EmailLocaleIDs = 'welcome_email' | 'email_heading';
  2. type FooterLocaleIDs = 'footer_title' | 'footer_sendoff';
  3. type AllLocaleIDs = `${EmailLocaleIDs | FooterLocaleIDs}_id`;
  4. // "welcome_email_id" | "email_heading_id" | "footer_title_id" | "footer_sendoff_id"

多个替换字符串的位置上的多个联合类型会进行交叉相乘:

  1. type EmailLocaleIDs = 'welcome_email' | 'email_heading';
  2. type FooterLocaleIDs = 'footer_title' | 'footer_sendoff';
  3. type AllLocaleIDs = `${EmailLocaleIDs | FooterLocaleIDs}_id`;
  4. type Lang = 'en' | 'ja' | 'pt';
  5. type LocaleMessageIDs = `${Lang}_${AllLocaleIDs}`;
  6. // type EmailLocaleIDs = "welcome_email" | "email_heading";
  7. type FooterLocaleIDs = 'footer_title' | 'footer_sendoff';
  8. type AllLocaleIDs = `${EmailLocaleIDs | FooterLocaleIDs}_id`;
  9. type Lang = 'en' | 'ja' | 'pt';
  10. type LocaleMessageIDs = `${Lang}_${AllLocaleIDs}`;
  11. // "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"

我们还是建议开发者要提前生成数量巨大的字符串联合类型,但如果数量较少,那么上面介绍的方法会有所帮助。

模版字面量的强大之处在于它能够基于给定的字符串来创建新的字符串。

注意,on会监听"firstNameChanged"事件,而不是"firstName"。 模版字面量提供了操作字符串类型的能力:

  1. type PropEventSource<Type> = {
  2. on(
  3. eventName: `${string & keyof Type}Changed`,
  4. ): void;
  5. };
  6. /// Create a "watched object" with an 'on' method
  7. /// so that you can watch for changes to properties.
  8. declare function makeWatchedObject<Type>(
  9. obj: Type
  10. ): Type & PropEventSource<Type>;

这样做之后,当传入了错误的属性名会产生一个错误:

  1. type PropEventSource<Type> = {
  2. on(
  3. eventName: `${string & keyof Type}Changed`,
  4. callback: (newValue: any) => void
  5. ): void;
  6. };
  7. declare function makeWatchedObject<T>(obj: T): T & PropEventSource<T>;
  8. const person = makeWatchedObject({
  9. firstName: 'Saoirse',
  10. lastName: 'Ronan',
  11. age: 26,
  12. person.on('firstNameChanged', () => {});
  13. // 以下存在拼写错误
  14. person.on('firstName', () => {});
  15. person.on('frstNameChanged', () => {});

模版字面量类型推断

注意,上例中没有使用原属性值的类型,在回调函数中仍使用any类型。 模版字面量类型能够从替换字符串的位置推断出类型。

下面,我们将上例修改成泛型,它会从eventName字符串来推断出属性名。

这里,我们将改为泛型方法。

当用户使用字符串"firstNameChanged'来调用时,TypeScript 会尝试推断K的类型。 为此,TypeScript 尝试将Key"Changed"之前的部分进行匹配,并且推断出字符串"firstName"。 当 TypeScript 推断出了类型后,on方法就能够获取firstName属性的类型,即string类型。 相似的,当使用"ageChanged"调用时,TypeScript 能够知道age属性的类型是number

为了方便字符串操作,TypeScript 提供了一系列操作字符串的类型。 这些类型内置于编译器之中,以便提高性能。 它们不存在于 TypeScript 提供的.d.ts文件中。

将字符串中的每个字符转换为大写字母。

Example
  1. type Greeting = 'Hello, world';
  2. type ShoutyGreeting = Uppercase<Greeting>;
  3. // "HELLO, WORLD"
  4. type ASCIICacheKey<Str extends string> = `ID-${Uppercase<Str>}`;
  5. type MainID = ASCIICacheKey<'my_app'>;
  6. // "ID-MY_APP"

Lowercase<StringType>

将字符串中的每个字符转换为小写字母。

  1. type Greeting = 'Hello, world';
  2. type QuietGreeting = Lowercase<Greeting>;
  3. // "hello, world"
  4. type ASCIICacheKey<Str extends string> = `id-${Lowercase<Str>}`;
  5. type MainID = ASCIICacheKey<'MY_APP'>;
  6. // "id-my_app"

将字符串中的首字母转换为大写字母。

Example

Uncapitalize<StringType>

将字符串中的首字母转换为小写字母。

Example

固有字符串操作类型的技术细节
  1. function applyStringMapping(symbol: Symbol, str: string) {
  2. switch (intrinsicTypeKinds.get(symbol.escapedName as string)) {
  3. case IntrinsicTypeKind.Uppercase: return str.toUpperCase();
  4. case IntrinsicTypeKind.Lowercase: return str.toLowerCase();
  5. case IntrinsicTypeKind.Capitalize: return str.charAt(0).toUpperCase() + str.slice(1);
  6. case IntrinsicTypeKind.Uncapitalize: return str.charAt(0).toLowerCase() + str.slice(1);
  7. }
  8. }