本篇翻译整理自 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"