• 阐明:目前网上没有 TypeScript 最新官网文档的中文翻译,所以有了这么一个翻译打算。因为我也是 TypeScript 的初学者,所以无奈保障翻译百分之百精确,若有谬误,欢送评论区指出;
  • 翻译内容:暂定翻译内容为 TypeScript Handbook,后续有空会补充翻译文档的其它局部;
  • 我的项目地址:TypeScript-Doc-Zh,如果对你有帮忙,能够点一个 star ~

本章节官网文档地址:Template Literal Types

模板字面量类型

模板字面量类型基于字符串字面量类型构建,能够通过联结类型拓展成多种字符串。

其语法和 JavaScript 中的模板字符串一样,但在 TypeScript 中用于示意类型。和具体的字面量类型一起应用的时候,模板字面量会通过拼接内容产生一个新的字符串字面量类型。

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"

如果模板字面量有多个插值地位,那么各地位上的联结类型之间会进行叉积运算,从而失去最终的类型:

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"

对于大型的字符串联结类型,咱们举荐你提前生成。对于较小的字符串联结类型,则能够应用下面例子中的办法生成。

类型中的字符串联结类型

模板字面量的弱小之处在于它可能基于类型中的已有信息定义一个新的字符串。

假如当初有一个 makeWatchedObject 函数,它能够给传入的对象增加一个 on 办法。在 JavaScript 中,该函数的调用形如:makeWatchedObject(baseObject)。其中,传入的对象参数相似上面这样:

const passedObject = {    firstName: 'Saoirse',    lastName: 'Ronan',    age: 26,};

行将增加给对象的 on 办法会承受两个参数,一个是 eventName(字符串),一个是 callBack(回调函数)。

eventName 的模式相似于 attributeInThePassedObject + 'Changed'。比如说,传入对象有个 firstName 属性,那么对应就会有一个叫做 firstNameChangedeventName

callBack 回调函数,在被调用的时候会:

  • 承受一个参数,参数的类型和 attributeInThePassedObject 的类型相关联。比如说,firstName 的类型是 string,那么 firstNameChanged 这个事件对应的回调函数在被调用的时候也冀望承受一个 string 类型的参数。同理,和 age 相关联的事件回调函数在被调用的时候应该承受一个 number 类型的参数。
  • 返回值类型为 void(为了不便例子的解说)

on() 的简易版函数签名可能是这样的:on(eventName: string, callBack: (newValue: any) => void)。不过,从下面的形容来看,咱们发现代码中还须要实现很重要的类型束缚。而模板字面量类型正好就能够帮忙咱们做到这一点。

const person = makeWatchedObject({  firstName: "Saoirse",  lastName: "Ronan",  age: 26,}); // makeWatchedObject 函数给匿名对象增加了 on 办法 person.on("firstNameChanged", (newValue) => {  console.log(`firstName was changed to ${newValue}!`);});

留神,on 监听的事件是 "firstNameChanged",而不是 "firstName"。如果咱们要确保符合条件的事件名的汇合受到对象属性名(开端加上“Changed”)的联结类型的束缚,那么咱们的简易版 on() 办法还须要进一步欠缺才行。尽管在 JavaScript 中咱们能够很不便地实现这个成果,比方应用 Object.keys(passedObject).map(x => ${x}Changed),不过,类型零碎中的模板字面量也提供了一种相似的操控字符串的办法:

type PropEventSource<Type> = {    on(eventName: `${string & keyof Type}Changed`, callback: (newValue: any) => void): void;}; // 创立一个带有 on 办法的监听对象,从而监听对象属性的变动declare function makeWatchedObject<Type>(obj: Type): Type & PropEventSource<Type>;

这样,当传入谬误的参数时,TypeScript 会抛出一个谬误:

const person = makeWatchedObject({  firstName: "Saoirse",  lastName: "Ronan",  age: 26}); person.on("firstNameChanged", () => {}); // 预防常见的人为谬误(谬误地应用了对象的属性名而不是事件名)person.on("firstName", () => {});// Argument of type '"firstName"' is not assignable to parameter of type '"firstNameChanged" | "lastNameChanged" | "ageChanged"'. // 拼写错误person.on("frstNameChanged", () => {});// Argument of type '"frstNameChanged"' is not assignable to parameter of type '"firstNameChanged" | "lastNameChanged" | "ageChanged"'.

模板字面量的推断

留神,目前为止咱们还没有齐全利用传入对象提供的信息。firstName 扭转的时候(触发 firstNameChanged 事件),咱们冀望回调函数会承受一个 string 类型的参数。同理,age 扭转的时候,对应的回调函数也会承受一个 number 类型的参数。但目前,咱们仅仅只是用 any 作为回调函数参数的类型而已。这里咱们须要再次应用模板字面量类型,它能够确保属性的数据类型和属性对应的回调函数的参数类型保持一致。

实现这一点的关键在于:咱们能够应用一个带有泛型的函数,从而确保:

  1. 第一个参数中的字面量能够被捕捉为一个字面量类型
  2. 泛型的无效属性会形成一个联结类型,能够验证捕捉的字面量类型是否是该联结类型的一个成员
  3. 能够在泛型构造中通过按索引拜访的形式去查看已验证属性的类型
  4. 该类型信息能够被进一步利用,以保障回调函数的参数也是雷同的类型
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");    }})

这里咱们让 on 变成了一个泛型办法。

当开发者通过字符串 "firstNameChanged" 调用了 on 办法的时候,TypeScript 会尝试推断出 Key 的正确类型。具体地说,它会将 Key"Changed" 后面的局部进行匹配,并推断出字符串 "firstName"。一旦 TypeScript 推断实现,on 办法就能够取出原对象的 firstName 属性的类型 —— 即 string 类型。同理,当通过 "ageChanged" 调用办法的时候,TypeScript 也会发现 age 属性的类型是 number

推断有多种不同的联合形式,通常用于解构字符串,并以不同的形式对字符串进行重构。

内建的字符串操控类型

为了不便操控字符串,TypeScript 引入了一些相干的类型。为了进步性能,这些类型是内建到编译器中的,并且无奈在 TypeScript 附带的 .d.ts 文件中找到。

Uppercase<StringType>

将字符串中的每个字符转化为大写模式。

示例:

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'

Lowercase<StringType>

将字符串中的每个字符转化为小写模式。

示例:

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"

Capitalize<StringType>

将字符串的首字符转化为大写模式。

示例:

type LowercaseGreeting = 'hello, world';type Greeting = Capitalize<LowercaseGreeting>;        ^       // type Greeting = 'Hello, world'     

Uncapitalize<StringType>

将字符串的首字符转化为小写模式。

示例:

type UppercaseGreeting = 'HELLO WORLD';type UncomfortableGreeting = Uncapitalize<UppercaseGreeting>;           ^              // type UncomfortableGreeting = "hELLO WORLD"     

对于内建字符串操控类型的一些技术细节:

从 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;}