了解TypeScript数据类型中的模板字面量
模板字面量类型(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 Objectperson.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 PRopEventSourceType>
= {
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 makeWatchedObjectType>
(obj: Type): Type &
PropEventSourceType>
;
注意,我们在这里例子中,模板字面量里我们写的是 string &
keyof Type,我们可不可以只写成 keyof Type 呢?如果我们这样写,会报错:
type PropEventSourceType>
= {
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 PropEventSourceType>
= {
on(eventName: `${
Excludekeyof Type, symbol>
}
Changed`, callback: (newValue: any) =>
void): void;
}
;
再或者这样写:
type PropEventSourceType>
= {
on(eventName: `${
Extractkeyof 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-resistantperson.on("frstNameChanged", () =>
{
}
);
// argument of type '"frstNameChanged"' is not assignable to parameter of type '"firstNameChanged" | "lastNameChanged" | "ageChanged"'.模板字面量的推断(Inference with Template Literals)
现在我们来实现第二点,回调函数传入的值的类型与对应的属性值的类型相同。我们现在只是简单的对 callBack 的参数使用 any 类型。实现这个约束的关键在于借助泛型函数:
捕获泛型函数第一个参数的字面量,生成一个字面量类型
该字面量类型可以被对象属性构成的联合约束
对象属性的类型可以通过索引访问获取
应用此类型,确保回调函数的参数类型与对象属性的类型是同一个类型
type PropEventSourceType>
= {
onKey extends string &
keyof Type>
(eventName: `${
Key}
Changed`, callback: (newValue: Type[Key]) =>
void ): void;
}
;
declare function makeWatchedObjectType>
(obj: Type): Type &
PropEventSourceType>
;
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 文件里找到它们。
UppercaseStringType>
把每个字符转为大写形式:
type Greeting = "Hello, world"type ShoutyGreeting = UppercaseGreeting>
// type ShoutyGreeting = "HELLO, WORLD" type ASCIICacheKeyStr extends string>
= `ID-${
UppercaseStr>
}
`type MainID = ASCIICacheKey"my_app">
// type MainID = "ID-MY_APP"LowercaseStringType>
把每个字符转为小写形式:
type Greeting = "Hello, world"type QuietGreeting = LowercaseGreeting>
// type QuietGreeting = "hello, world" type ASCIICacheKeyStr extends string>
= `id-${
LowercaseStr>
}
`type MainID = ASCIICacheKey"MY_APP">
// type MainID = "id-my_app"CapitalizeStringType>
把字符串的第一个字符转为大写形式:
type LowercaseGreeting = "hello, world";
type Greeting = CapitalizeLowercaseGreeting>
;
// type Greeting = "Hello, world"UncapitalizeStringType>
把字符串的第一个字符转换为小写形式:
type UppercaseGreeting = "HELLO WORLD";
type UncomfortableGreeting = UncapitalizeUppercaseGreeting>
;
// 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;
}
【相关推荐:javascript学习教程】
以上就是了解TypeScript数据类型中的模板字面量的详细内容,更多请关注其它相关文章!
声明:本文内容由网友自发贡献,本站不承担相应法律责任。对本内容有异议或投诉,请联系2913721942#qq.com核实处理,我们将尽快回复您,谢谢合作!
若转载请注明出处: 了解TypeScript数据类型中的模板字面量
本文地址: https://pptw.com/jishu/593348.html
