模板字面量类型

模板字面量类型建立在 字符串字面量类型 上,并能够通过联合扩展为许多字符串。

它们与 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函数需要两个参数,一个eventName(一个string)和一个callback(一个function)。

eventName应采用attributeInThePassedObject + "Changed"的形式;因此,从基本对象中的属性firstName派生的firstNameChanged

callback函数,当被调用时

  • 应传递一个与名称attributeInThePassedObject关联的类型的值;因此,由于firstName被类型化为stringfirstNameChanged事件的回调期望在调用时传递一个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监听事件"firstNameChanged",而不仅仅是"firstName"。如果我们要确保合格事件名称的集合受观察对象中属性名称的并集(在末尾添加“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参数。我们天真地使用any来对callback的参数进行类型化。同样,模板文字类型使我们能够确保属性的数据类型与该属性回调的第一个参数的类型相同。

使这成为可能的关键见解是:我们可以使用一个带有泛型的函数,这样

  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 文档是一个开源项目。帮助我们改进这些页面 通过发送拉取请求

此页面的贡献者
SHSteven Harms (6)
OTOrta Therox (4)
SGHSteven G. Harms (3)
SPSeol Park (1)
PPenchy (1)
6+

上次更新:2024 年 3 月 21 日