TypeScript 的官网文档早已更新,但我能找到的中文文档都还停留在比拟老的版本。所以对其中新增以及订正较多的一些章节进行了翻译整顿。
本篇翻译整顿自 TypeScript Handbook 中「Template Literal Types」章节。
本文并不严格依照原文翻译,对局部内容也做了解释补充。
模板字面量类型(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"
如果模板字面量里的多个变量都是联结类型,后果会穿插相乘,比方上面的例子就有 2 2 3 一共 12 种后果:
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 Unions in Types)
模板字面量最有用的中央在于你能够基于一个类型外部的信息,定义一个新的字符串,让咱们举个例子:
有这样一个函数 makeWatchedObject
,它会给传入的对象增加了一个 on
办法。在 JavaScript 中,它的调用看起来是这样:makeWatchedObject(baseObject)
,咱们假如这个传入对象为:
const passedObject = {
firstName: "Saoirse",
lastName: "Ronan",
age: 26,
};
这个 on
办法会被增加到这个传入对象上,该办法承受两个参数,eventName
(string
类型)和 callBack
(function
类型):
// 伪代码
const result = makeWatchedObject(baseObject);
result.on(eventName, callBack);
咱们心愿 eventName
是这种模式:attributeInThePassedObject + "Changed"
,举个例子,passedObject
有一个属性 firstName
,对应产生的 eventName
为 firstNameChanged
,同理,lastName
对应的是 lastNameChanged
,age
对应的是 ageChanged
。
当这个 callBack
函数被调用的时候:
- 应该被传入与
attributeInThePassedObject
雷同类型的值。比方passedObject
中,firstName
的值的类型为string
, 对应firstNameChanged
事件的回调函数,则承受传入一个string
类型的值。age
的值的类型为number
,对应ageChanged
事件的回调函数,则承受传入一个number
类型的值。 - 返回值类型为
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"
,而回调函数传入的值 newValue
,咱们心愿束缚为 string
类型。咱们先实现第一点。
在这个例子里,咱们心愿传入的事件名的类型,是对象属性名的联结,只是每个联结成员都还在最初拼接一个 Changed
字符,在 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>;
留神,咱们在这里例子中,模板字面量里咱们写的是 string & keyof Type
,咱们可不可以只写成 keyof Type
呢?如果咱们这样写,会报错:
type PropEventSource<Type> = {on(eventName: `${keyof Type}Changed`, callback: (newValue: any) => void): void;
};
// Type 'keyof Type' is not assignable to type 'string | number | bigint | boolean | null | undefined'.
// Type 'string | number | symbol' is not assignable to type 'string | number | bigint | boolean | null | undefined'.
// ...
从报错信息中,咱们也能够看出报错起因,在《TypeScript 系列之 Keyof 操作符》里,咱们晓得 keyof
操作符会返回 string | number | symbol
类型,然而模板字面量的变量要求的类型却是 string | number | bigint | boolean | null | undefined
,比拟一下,多了一个 symbol 类型,所以其实咱们也能够这样写:
type PropEventSource<Type> = {on(eventName: `${Exclude<keyof Type, symbol>}Changed`, callback: (newValue: any) => void): void;
};
再或者这样写:
type PropEventSource<Type> = {on(eventName: `${Extract<keyof Type, string>}Changed`, callback: (newValue: any) => void): void;
};
应用这种形式,在咱们应用谬误的事件名时,TypeScript 会给出报错:
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 Template Literals)
当初咱们来实现第二点,回调函数传入的值的类型与对应的属性值的类型雷同。咱们当初只是简略的对 callBack
的参数应用 any
类型。实现这个束缚的关键在于借助泛型函数:
- 捕捉泛型函数第一个参数的字面量,生成一个字面量类型
- 该字面量类型能够被对象属性形成的联结束缚
- 对象属性的类型能够通过索引拜访获取
- 利用此类型,确保回调函数的参数类型与对象属性的类型是同一个类型
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
正确的类型。它会匹配 key
和 "Changed"
前的字符串,而后推断出字符串 "firstName"
,而后再获取原始对象的 firstName
属性的类型,在这个例子中,就是 string
类型。
内置字符操作类型(Intrinsic String Manipulation Types)
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 字符串运行时函数,而不是本地化辨认 (locale aware)。
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 系列
- TypeScript 之 根底入门
- TypeScript 之 常见类型(上)
- TypeScript 之 常见类型(下)
- TypeScript 之 类型收窄
- TypeScript 之 函数
- TypeScript 之 对象类型
- TypeScript 之 泛型
- TypeScript 之 Keyof 操作符
- TypeScript 之 Typeof 操作符
- TypeScript 之 索引拜访类型
- TypeScript 之 条件类型
- TypeScript 之 映射类型
微信:「mqyqingfeng」,加我进冴羽惟一的读者群。
如果有谬误或者不谨严的中央,请务必给予斧正,非常感激。如果喜爱或者有所启发,欢送 star,对作者也是一种激励。