typescript团队发布了typescript 4.1,其中包括功能强大的模板字面量类型、映射类型的键重映射以及递归条件类型。下面本篇文章就来带大家了解一下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 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-resistantperson.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;}
【相关推荐:javascript学习教程】
以上就是了解typescript数据类型中的模板字面量的详细内容。