TypeScript 之 模板字面量

847 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第3天,点击查看活动详情

前言

今天来介绍一下什么是 TS 中的模板字面量类型,引入它是为了解决什么实际中的问题,听到模板字面量,首先想到的应该就是 JS 中的模板字符串,那么它们两个之间又有什么关系,并且模板字面量还可以结合我们之前学过的其他 TS 类型来实现强大的类型工具,相信在读完这篇文章之后,你对这些都能有一个大概的了解

什么是模板字面量类型 ( Template Literal Types )

typeScript 提供给我们的,用于字符串字面量扩展的有效工具

模板文本类型基于字符串字面量类型构建,并且能够通过联合扩展到多种字符串字面量类型。

语法:

`${T}` 

注意这里的 T 是类型占位,我们不知可以传入字符串类型还可以传入非字符串的基本类型。

T 传入的基本类型,TS 会帮我们转换为字符串类型。

type A = `${1}`
// type A = "1"
type B = `${'a'}`
// type B = "a"
type C = `${true}`
// type C = "true"
type D = `${null}`
// type D = "null"
type E = `${undefined}`
// type E = "undefined"

模板字面量类型有什么用,用来解决什么问题?

比如说在我们平时的开发当中,有时候会用到字符串字面量类型来对一个参数做出限制:

type EmailLocale = "welcome_email" | "email_heading";

type FooterLocale = "footer_title" | "footer_sendoff";

当我们要在这些类型后面添加上id来代表它的id的字面量类型的时候,在此之前你可能会定义两个新的类型:

type EmailLocaleIDs = "welcome_email_id" | "email_heading_id";

type FooterLocaleIDs = "footer_title_id" | "footer_sendoff_id";

并且在这之后,如果有一个类型需要整合这些所有的字面量类型,你就可能需要再定义一个类型:

type AllLocaleIDs = "welcome_email_id" | "email_heading_id | footer_title_id" | "footer_sendoff_id";

在这个时候,我们就能够发现了,上面的类型当中,存在着很多的重复代码,我们是否能够将这些重复代码抽成参数,通过传参的方式来传入呢?

这样就能够在定义新的字面量类型的时候,节省下大部分的代码量了。

通过 JS 模板字面量来对比

对于 JS 的模板字符串,相信大部分人都有使用过,那么它和 TS 的模板字面量又有着什么不同呢

在 JS 中,我们常常会用模板字符串来拼接字符串,在早期没有模板字符串的时候,你可能会是这样凭借字符串的:

let userName = "小明"
console.log("我是"+userName)

image.png

这样子不好的地方就在于,万一需要凭借的变量比较多,那么写这个拼接就会变得非常麻烦

image.png

在 JS 引入模板字符串之后,给我们提供了更加方便也更加直观的方式来拼接字符串:

let userName = "小明"
let age = 18
console.log("我是"+userName+"我今年"+age)
console.log(`我是${userName}我今年${age}`)

image.png

那么对于 TS 来说,模板字面量的语法和 JS 的模板字符串语法是完全相同的,只不过在 TS 里,我们操作的不是值,而是类型,用于在与具体文本类型一起使用,模板文本通过连接内容来生成新的字符串文本类型。

type World = "world";

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

模板字面量类型的作用

看完上面对于模板字面量的简单讲解,是不是发现,我们可以通过这个类型来凭借字符串字面量,从而减少重复代码

type EmailLocale = "welcome_email";

type AddId<T extends string> = `${T}_id`;

type EmailLocaleIDs = AddId<EmailLocale>
// type EmailLocaleIDs = "welcome_email_id"

并且当你传入的类型是一个联合类型的时候,模板字面量会进行分布计算:

type EmailLocale = "welcome_email" | "email_heading";

type AddId<T extends string> = `${T}_id`;

type EmailLocaleIDs = AddId<EmailLocale>
// type EmailLocaleIDs = "welcome_email_id" | "email_heading_id"

我们还可以传入多个类型的联合类型,这样就能够解决文章刚开始说的那个问题,

type EmailLocale = "welcome_email" | "email_heading";

type FooterLocale = "footer_title" | "footer_sendoff";

type AddId<T extends string> = `${T}_id`;

type EmailLocaleIDs = AddId<EmailLocale>
// type EmailLocaleIDs = "welcome_email_id" | "email_heading_id"

type FooterLocaleIDs  = AddId<FooterLocale>
// type FooterLocaleIDs = "footer_title_id" | "footer_sendoff_id"

type AllLocaleIDs = AddId<EmailLocale | FooterLocale>
// type AllLocaleIDs = "welcome_email_id" | "email_heading_id" | "footer_title_id" | "footer_sendoff_id"

结合重映射实现键值重命名

在平时的日常开发当中,可能会有一些对象,它里面保存着一些对象属性,并且我们需要对它属性进行修改的方法,比如说最常见的 get set,那么我们可能就会定义一个对象,内部存着它的属性,在定义另一个对象,用于保存get方法,再定义一个对象,用于保存set方法,这样无疑会写出很多的重复代码,举一个例子:

type User = {
    name:string
    age:number
}

type GetUser = {
    GetName:()=>string
    GetAge:()=>number
}

定义一个对象以及保存获取对象属性方法的对象,一般来说会这样子定义两个对象,这明显有着很多的重复代码:

image.png

所有我们就能够使用重映射加上模板字面量来实现一个工具类型,帮助我们快速的新建一个新对象:

type User = {
    name:string
    age:number
}

type Getters<T> = {
    [P in keyof T as `get${Capitalize<string & P>}`]:()=>T[P]
}

type GetUser = Getters<User>
// type GetUser = {
//     getName: () => string;
//     getAge: () => number;
// }

这个工具类型中还涉及到了交叉类型的知识点,不同的小伙伴可以先去阅读一下之前的介绍联合类型和交叉类型作用的文章。

这样我们就实现了一个方便的工具类型,能够一键生成一个新的保存get方法的新对象。

总结

本文简单的介绍了模板字面量,模板字面量的语法和JS模板字符串是一样的,但是由于TS有联合类型的存在,所有在TS当中,模板字面量能够更加的灵活多变,熟练的使用模板字面量,能够使你在定义字面量类型的时候省去很多的功夫。