模板字面量类型建立在 字符串字面量类型 上,并能够通过联合扩展为许多字符串。
它们与 JavaScript 中的模板字面量字符串 具有相同的语法,但在类型位置使用。当与具体字面量类型一起使用时,模板字面量会通过串联内容生成一个新的字符串字面量类型。
tsTry
typeWorld = "world";typeGreeting = `hello ${World }`;
当在插值位置使用联合时,类型是每个联合成员可以表示的每个可能字符串字面量的集合
tsTry
typeEmailLocaleIDs = "welcome_email" | "email_heading";typeFooterLocaleIDs = "footer_title" | "footer_sendoff";typeAllLocaleIDs = `${EmailLocaleIDs |FooterLocaleIDs }_id`;
对于模板字面量中的每个插值位置,联合都会交叉相乘
tsTry
typeAllLocaleIDs = `${EmailLocaleIDs |FooterLocaleIDs }_id`;typeLang = "en" | "ja" | "pt";typeLocaleMessageIDs = `${Lang }_${AllLocaleIDs }`;
我们通常建议人们对大型字符串联合使用提前生成,但这在较小的案例中很有用。
类型中的字符串联合
模板字面量的强大之处在于根据类型内部的信息定义新的字符串。
考虑一个函数 (makeWatchedObject
) 向传递的对象添加一个名为 on()
的新函数的情况。在 JavaScript 中,它的调用可能看起来像:makeWatchedObject(baseObject)
。我们可以想象基本对象看起来像
tsTry
constpassedObject = {firstName : "Saoirse",lastName : "Ronan",age : 26,};
将添加到基本对象中的on
函数需要两个参数,一个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)
。但是,在前面的描述中,我们确定了我们希望在代码中记录的重要类型约束。模板文字类型让我们将这些约束带入我们的代码。
tsTry
constperson =makeWatchedObject ({firstName : "Saoirse",lastName : "Ronan",age : 26,});// makeWatchedObject has added `on` to the anonymous Objectperson .on ("firstNameChanged", (newValue ) => {console .log (`firstName was changed to ${newValue }!`);});
注意,on
监听事件"firstNameChanged"
,而不仅仅是"firstName"
。如果我们要确保合格事件名称的集合受观察对象中属性名称的并集(在末尾添加“Changed”)约束,那么我们对on()
的朴素规范可以变得更加健壮。虽然我们很乐意在 JavaScript 中进行这样的计算,例如Object.keys(passedObject).map(x => `${x}Changed`)
,但类型系统内部的模板文字提供了一种类似的字符串操作方法
tsTry
typePropEventSource <Type > = {on (eventName : `${string & keyofType }Changed`,callback : (newValue : any) => void): void;};/// Create a "watched object" with an `on` method/// so that you can watch for changes to properties.declare functionmakeWatchedObject <Type >(obj :Type ):Type &PropEventSource <Type >;
有了它,我们可以构建一些在给出错误属性时会报错的东西
tsTry
constperson =makeWatchedObject ({firstName : "Saoirse",lastName : "Ronan",age : 26});person .on ("firstNameChanged", () => {});// Prevent easy human error (using the key instead of the event name)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"'.person .on ("firstName" , () => {});// It's typo-resistantArgument 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"'.person .on ("frstNameChanged" , () => {});
模板文字推断
注意,我们没有从原始传递对象中提供的所有信息中获益。鉴于firstName
的更改(即firstNameChanged
事件),我们应该期望回调将接收一个类型为string
的参数。类似地,age
更改的回调应该接收一个number
参数。我们天真地使用any
来对callback
的参数进行类型化。同样,模板文字类型使我们能够确保属性的数据类型与该属性回调的第一个参数的类型相同。
使这成为可能的关键见解是:我们可以使用一个带有泛型的函数,这样
- 第一个参数中使用的字面量被捕获为字面量类型
- 该字面量类型可以被验证为在泛型中的有效属性的联合中
- 可以使用索引访问在泛型的结构中查找已验证属性的类型
- 然后,此类型信息可以应用于确保回调函数的参数具有相同的类型
tsTry
typePropEventSource <Type > = {on <Key extends string & keyofType >(eventName : `${Key }Changed`,callback : (newValue :Type [Key ]) => void): void;};declare functionmakeWatchedObject <Type >(obj :Type ):Type &PropEventSource <Type >;constperson =makeWatchedObject ({firstName : "Saoirse",lastName : "Ronan",age : 26});person .on ("firstNameChanged",newName => {console .log (`new name is ${newName .toUpperCase ()}`);});person .on ("ageChanged",newAge => {if (newAge < 0) {console .warn ("warning! negative age");}})
在这里,我们将on
变成了一个泛型方法。
当用户使用字符串"firstNameChanged"
调用时,TypeScript 将尝试推断Key
的正确类型。为此,它将匹配Key
与"Changed"
之前的內容,并推断字符串"firstName"
。一旦 TypeScript 弄清楚这一点,on
方法就可以获取原始对象上firstName
的类型,在本例中为string
。类似地,当使用"ageChanged"
调用时,TypeScript 会找到属性age
的类型,即number
。
推断可以以不同的方式组合,通常用于解构字符串,并以不同的方式重建它们。
内建字符串操作类型
为了帮助进行字符串操作,TypeScript 包含了一组类型,这些类型可以在字符串操作中使用。这些类型内置于编译器中以提高性能,并且在 TypeScript 附带的.d.ts
文件中找不到。
Uppercase<StringType>
将字符串中的每个字符转换为大写版本。
示例
tsTry
typeGreeting = "Hello, world"typeShoutyGreeting =Uppercase <Greeting >typeASCIICacheKey <Str extends string> = `ID-${Uppercase <Str >}`typeMainID =ASCIICacheKey <"my_app">
Lowercase<StringType>
将字符串中的每个字符转换为小写等效字符。
示例
tsTry
typeGreeting = "Hello, world"typeQuietGreeting =Lowercase <Greeting >typeASCIICacheKey <Str extends string> = `id-${Lowercase <Str >}`typeMainID =ASCIICacheKey <"MY_APP">
Capitalize<StringType>
将字符串中的第一个字符转换为大写等效字符。
示例
tsTry
typeLowercaseGreeting = "hello, world";typeGreeting =Capitalize <LowercaseGreeting >;
Uncapitalize<StringType>
将字符串中的第一个字符转换为小写等效字符。
示例
tsTry
typeUppercaseGreeting = "HELLO WORLD";typeUncomfortableGreeting =Uncapitalize <UppercaseGreeting >;
有关内在字符串操作类型的技术细节
截至 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;
}