模板字符类型(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"
在模板字面量
的每个插值
的位置,联合类型
会被交叉相乘
:
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"
在类型中使用字符联合(String Uniond in Types)
当基于类型
内的信息定义一个新字符串
时,模板字面量
的好处就体现出来了。
考虑实现一个功能,让makeWatchedObject
函数为传入的对象添加一个on()
函数。在JavaScript
中,类似的形式就是:makeWatchedObject(baseObject)
。我们可以把基础的对象看作是:
const passedObject = {
firstName: "Saoirse",
lastName: "Ronan",
age: 26,
};
on
函数将被添加到passedObject
上,并传递两个参数:eventName
和callBack
。
eventName
由passedObject的属性
+'Changed'
组成。因此,firstNameChanged
在基础对象的firstName
属性衍生出来。
callBack
被调用时:
passedObject
的属性被复制
;因此,由于firstName
是string
类型,那么当调用它时,firstNameChanged
函数希望传递一个string
类型的值。age
类似。- 应该返回
void
on
函数的==调用签名==看起来应该是:on(eventName: string, callBack: (newValue: any) => void)
。实际上,在前一个描述当中,我们确定了在代码中的重要类型约束
。模板字面量类型
解决了我们需要的==约束方式==。
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}!`);
});
请注意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;
};
/// 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>;
这样,我们就可以在赋予错误的属性
时,给出错误:
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"'.
// It's typo-resistant
person.on("frstNameChanged", () => {});
// Argument of type '"frstNameChanged"' is not assignable to parameter of type '"firstNameChanged" | "lastNameChanged" | "ageChanged"'.
字符模板和接口(Inference with Tempalte Literals)
firstName
的变成了firstNameChanged
,但是我们同时想让firstNameChanged
函数接受string
类型的参数。类似的,age
的ageChanged
应该接受一个number
类型的参数。我们可能自然的想到用any
来作为callBack
的参数类型。不过,模板字符类型
可以实现属性的回调函数的第一个参数的类型和属性的类型保持一致。
我们敏锐的察觉到:我们可以使用一个带有泛型的函数:
- 第一个参数被捕获为
字面量类型
- 这个
文字类型
可以被校验,确保一定在泛型的属性
的联合类型中 - 已经被验证的
属性
的类型可以使用索引访问
的方式在泛型
中找到 - 之后可以应用这个
类型信息
确保和回调函数
的参数属于同一类型
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
时,TypeScript
将尝试为Key
推导正确的类型。为了达到这个目的,TypeScript
会把Key
和Changed
之前的内容进行匹配,并推导出字符串firstName
。on
方法可以拿到firstName
的类型。类似的"ageChanged",TypeScript
会把age
的number
类型赋予"ageChanged"的参数。
推理可以以不同的方式组合,通常是结构字符串
,并以不同的方式重构它们。
内置的字符串操作类型(Intrinsic String Manipulation Types)
为了操纵string
类型,TypeScript
包含了一些列类型的设置可以被用在类型操作
上。这些类型内置于编译器
以提高性能,在TypeScript
包含的.d.ts
文件中找不到。
Uppercase<StringType>
转换每个字符
成为大写
。
Example
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>
转换每个字符
成为小写
。
Example
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>
转换第一个字符
成为大写
。
Example
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: Documentation - Template Literal Types (typescriptlang.org))