TS文档学习 --- 模板字面量类型

366 阅读2分钟

本篇翻译整理自 TypeScript Handbook 中 「Template Literal Types」 章节。

模板字面量类型

模板字面量类型以字符串字面量类型为基础,可以通过联合类型扩展成多个字符串。

它们跟 JavaScript 的模板字符串是相同的语法,但是只能用在类型操作中。当使用模板字面量类型时,它会替换模板中的变量,

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

当模板中的变量是一个联合类型时,每一个可能的字符串字面量都会被表示

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"

// 如果模板字面量里的多个变量都是联合类型,结果会交叉相乘
// 比如下面的例子就有 2 * 2 * 3 一共 12 种结果:
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"

类型中的字符串联合类型

模板字面量最有用的地方在于你可以基于一个类型内部的信息,定义一个新的字符串

让我们举个例子: 有这样一个函数 makeWatchedObject, 它会给传入的对象添加了一个 on 方法。

我们假设这个传入类型为:

const passedObject = {
  firstName: string,
  lastName: string,
  age: number
};

输出的对象类型为

const passedObject = {
  firstName: string,
  lastName: string,
  age: number,
  
  firstNameChanged: (v: string) => void,
  lastNameChanged: (v: string) => void,
  ageChanged: (v: number) => void
};

我们可以进行如下约束:

type PropEventSource<Type> = {
    // 之所以需要使用string & keyof Type是因为
    // keyof Type --> string | number | symbol
    // 模板字面量的变量要求的类型却是 string | number | bigint | boolean | null | undefined
    // string & keyof Type --> 去除了symbol类型的属性名,同时去除了数值类型的属性名
    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");
    }
})

内置字符操作类型

TypeScript 的一些类型可以用于字符操作,这些类型处于性能的考虑被内置在编译器中,你不能在 .d.ts 文件里找到它们。

// Uppercase --- 把每个字符转为大写形式
type Greeting = "Hello, world"
type ShoutyGreeting = Uppercase<Greeting>        
// type ShoutyGreeting = "HELLO, WORLD"

// -------------------------------------

// Lowercase --- 把每个字符转为小写形式
type Greeting = "Hello, world"
type QuietGreeting = Lowercase<Greeting>       
// type QuietGreeting = "hello, world"

// -------------------------------------

// Capitalize --- 把字符串的第一个字符转为大写形式
type LowercaseGreeting = "hello, world";
type Greeting = Capitalize<LowercaseGreeting>;
// type Greeting = "Hello, world"

// -------------------------------------

// Uncapitalize --- 把字符串的第一个字符转换为小写形式
type UppercaseGreeting = "HELLO WORLD";
type UncomfortableGreeting = Uncapitalize<UppercaseGreeting>;           
// type UncomfortableGreeting = "hELLO WORLD"