TypeScript類(lèi)型操作之字符串處理功能詳解
引言
本文主要介紹模板 TypeScript 對(duì)模板字符串的能力支持及實(shí)現(xiàn)原理,深入介紹了 TypeScript 提供的字符串操作能力。結(jié)合《TypeScript玩轉(zhuǎn)類(lèi)型操作之基礎(chǔ)篇》內(nèi)容,完整覆蓋了TS體操的能力基礎(chǔ)。附帶一步步實(shí)現(xiàn)將 aa_bb___cc 轉(zhuǎn)換成 aaBbCc 的類(lèi)型實(shí)現(xiàn)技巧。
模板字符串
模板字符串是 JS 語(yǔ)言提供了使用反引號(hào) ` 分割的字面量,支持多行字符、嵌入字符串插值表達(dá)式以及附帶標(biāo)簽(與標(biāo)簽函數(shù)異同使用)。
let str = `user name is : ${name}`; // 插值表達(dá)式 let str2 = ` line1.... line2.... ` ; // 多行字符 function str3Tag(all, ...expresitons) { console.log(all, expresitons); return all } const name = 'tony'; const age = 11; str3Tag`hello name is: ${str}, age is ${age}`) // all: ['hello name is: ', ', age is ', '', raw: Array(3)] //expressions: Array(0) (2) ['name', 11]
模板字符串在JS中的應(yīng)用不是本文我們的重點(diǎn),如果大家想深入了解一下可以先看看MDN然后再翻翻ECMAScript的規(guī)范基本上就很深入了。主要是介紹在 TypeScript 中,支持了哪些特性,提供了什么能力。
TS 中模板字符串的相關(guān)特性
當(dāng)模板字符串插值表達(dá)式用于類(lèi)型位置時(shí),具有以下特性:計(jì)算出來(lái)的模板字符串值會(huì)作為一個(gè)字符串固定類(lèi)型、如果插值表達(dá)式是一個(gè)聯(lián)合類(lèi)型那么將得到一個(gè)新的聯(lián)合類(lèi)型成員是插值聯(lián)合類(lèi)型成員與模板字符串靜態(tài)部分生成字符串并集。
構(gòu)造新的類(lèi)型
普通插值表達(dá)式,構(gòu)造一個(gè)復(fù)合型的字符串類(lèi)型。
type World = `world`; type Greeting = `hello ${World}`; // type Greeting = "hello world";
在插值位置使用聯(lián)合類(lèi)型時(shí),構(gòu)造出一個(gè)新的字符串聯(lián)合類(lèi)型。
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" */ // 換個(gè)位置構(gòu)造 type AllLocaleIDs = `${EmailLocaleIDs | FooterLocaleIDs}_id`; type Lang = "en" | "ja" | "pt"; type LocaleMessageIDs = `${Lang}_${AllLocaleIDs}`; /* type LocaleMessageIDs = | "ch_welcome_email_id" | "ch_email_heading_id" | "ch_footer_title_id" | "ch_footer_sendoff_id" | "en_welcome_email_id" | "en_email_heading_id" | "en_footer_title_id" | "en_footer_sendoff_id" | "jp_welcome_email_id" | "jp_email_heading_id" | "jp_footer_title_id" | "jp_footer_sendoff_id" */
TS 會(huì)動(dòng)態(tài)遍歷插值中的聯(lián)合類(lèi)型,然后構(gòu)造出所有情況下組合出來(lái)的并集聯(lián)合類(lèi)型。這在我們項(xiàng)目中組合出新的類(lèi)型挺實(shí)用。
使用模板字符串進(jìn)行推理
基于對(duì)象類(lèi)型內(nèi)部屬性名構(gòu)造字符串類(lèi)型,然后根據(jù)對(duì)象類(lèi)型中對(duì)應(yīng)的屬性值類(lèi)型進(jìn)行推導(dǎo)新的類(lèi)型。功能會(huì)很強(qiáng)大。我們以一個(gè)案例進(jìn)行解釋:題目是對(duì)一個(gè)對(duì)象進(jìn)行代理,代理后需要監(jiān)聽(tīng)到這個(gè)對(duì)象的變化事件,現(xiàn)在需要定義一套類(lèi)型來(lái)實(shí)現(xiàn)這個(gè)功能。
簡(jiǎn)化的JS代碼如下:
const tonyDefaultInfo = { name: 'tony', age: 11, }; function makeWatchedObject(target) { // 代理處理 ... return target; } const tony = makeWatchedObject(tonyDefaultInfo); tony.on('nameChanged', (newValue) => { console.log(newValue); });
現(xiàn)在我們需要使用類(lèi)型來(lái)對(duì) 調(diào)用 on 上注冊(cè)的事件名稱做檢查:要求on監(jiān)聽(tīng)的屬性名稱必須滿足(屬性名+ 'Changed' ) 格式,監(jiān)聽(tīng)函數(shù)返回值類(lèi)型和原對(duì)象一致【監(jiān)聽(tīng)name,返回值類(lèi)型是 string、監(jiān)聽(tīng)age返回值類(lèi)型是number】。我們分三步實(shí)現(xiàn)一下這個(gè)類(lèi)型的定義。
第一步:如何修飾 makeWatchedObject 函數(shù)的類(lèi)型,定義其返回值類(lèi)型為傳入的值類(lèi)型。
declare function makeWatchedObject<Type>(obj: Type): Type & { on(eventName: string, callback:(newValue: any)=>void):void };
第二步:定義一個(gè)類(lèi)型工具,負(fù)責(zé)提取類(lèi)型上的屬性作為模板字符串的插值。大家不清楚 extends 、 keyof 用法的請(qǐng)看這篇文章 《TypeScript 玩轉(zhuǎn)類(lèi)型操作之基礎(chǔ)篇》,里面詳細(xì)介紹了其使用場(chǎng)景以及方式,包含與 typeof、infer 等關(guān)鍵詞的配合使用以及Omit等高級(jí)類(lèi)型的源碼解析。
type PickPropertyNameComposeChange<T extends {[k:string]:any}> = { [K in keyof T]: `${K}Changed`; } type Person = { name: string; age: number } type ChangeKeys = PickPropertyNameComposeChange<Person> /* type ChangeKeys = { name: "nameChanged"; age: "ageChanged"; } */
第三步:將屬性名提取封裝成字符串用于on函數(shù)參數(shù)的修飾, 并提取目標(biāo)類(lèi)型上的值類(lèi)型作為callback參數(shù)的類(lèi)型。
// 定義一個(gè)工具類(lèi)型,特殊處理定義一個(gè) on 函數(shù)的類(lèi)型,主要是修飾 on 的參數(shù)類(lèi)型 type PropertiesEventChange<T> = { on<K extends string & keyof T >(eventName: `${K}Changed`, callback: (newValue:T[K]) => void):void } declare function makeWatchedObject<Type>(obj: Type): Type & PropertiesEventChange<Type>; const p = makeWatchedObject({ name: 'tony', age: 11 }); p.on('ageChanged', (age) => age); // (method) on<"age">(eventName: "ageChanged", callback: (newValue: number) => void): void
模板字符串的解析原理
TypeScript 在編譯過(guò)程中,編譯器會(huì)將代碼解析成 AST (抽象語(yǔ)法樹(shù))。對(duì)于模板字符串編譯器會(huì)識(shí)別出模板字符串的靜態(tài)部分(普通的字符文本)和動(dòng)態(tài)部分(插值表達(dá)式)解析的結(jié)果仍然是AST結(jié)構(gòu),完成解析以后TypeScript 會(huì)進(jìn)行類(lèi)型推導(dǎo)。類(lèi)型推導(dǎo)時(shí)編譯器會(huì)分析每個(gè)插值表達(dá)式的類(lèi)型,根據(jù)類(lèi)型不同進(jìn)行轉(zhuǎn)換推導(dǎo)出整個(gè)模板字符串的類(lèi)型。這里會(huì)有類(lèi)似于 JavaScript 類(lèi)型轉(zhuǎn)換的場(chǎng)景非字符如何 toPrimitive 。
TS代碼:
const name = 'Alice'; const age = 30; const message = `Hello, my name is ${name} and I am ${age} years old.`;
解析成類(lèi)似AST語(yǔ)法樹(shù):
TemplateLiteral └── Quasi (Hello, my name is ) └── Expression (name) └── Quasi ( and I am ) └── Expression (age) └── Quasi ( years old.)
解析模板字符串的結(jié)構(gòu),然后根據(jù)插值表達(dá)式的類(lèi)型推導(dǎo)出整個(gè)模板字符串的類(lèi)型。
字符串操作工具類(lèi)型【有的說(shuō)是高級(jí)類(lèi)型】
TypeScript 為了提升對(duì)字符串的操作能力(大小寫(xiě)轉(zhuǎn)換、首字母大小寫(xiě)轉(zhuǎn)換),內(nèi)置封裝了四個(gè)主要的工具類(lèi)型。分別是 Uppercase<StringType>、Lowercase<StringType>、Capitalize<StringType>、Uncapitalize<StringType> 這些類(lèi)型并不是像 Omit、Record 等高級(jí)類(lèi)型在 type.d.ts 中自定義封裝的,Typescript 為了性能將其內(nèi)置在編譯器中實(shí)現(xiàn)【源碼實(shí)在沒(méi)看明白,抱歉了諸位】。
我們來(lái)分別看看他們的功能:
Uppercase<StringType>
將字符串中的每個(gè)字母都轉(zhuǎn)換成大寫(xiě)。
type Greeting = "Hello, world" type ShoutyGreeting = Uppercase<Greeting> //type ShoutyGreeting = "HELLO, WORLD"
我們來(lái)實(shí)現(xiàn)這樣一個(gè)簡(jiǎn)單的題目:將一個(gè)對(duì)象上的所有屬性名提取出來(lái),并轉(zhuǎn)成大寫(xiě)。
// 限制一下K從T的屬性中提取,并且是string類(lèi)型 type PickPropertyToUppercase<T, K extends string & keyof T> = Uppercase<K>; const per = { name: 'tony', age: 11, } type PerType = PickPropertyToUppercase<typeof per, keyof typeof per> // type PerType = "NAME" | "AGE" // 如果你想只有一個(gè)入口,也可以這樣再封一個(gè)自定義的類(lèi)型工具 type A<T extends { [k: string ]: any }> = PickPropertyToUppercase<T, keyof T>; type APerType = A<typeof per> //type APerType = "NAME" | "AGE"
這些基礎(chǔ)能力結(jié)合我們上一篇文章中的類(lèi)型基礎(chǔ)【keyof、 typeof、infer、extends、條件類(lèi)型~】就是我們玩轉(zhuǎn)體操的基礎(chǔ)啦~
Lowercase<StringType>
將字符串中所有的字母都變成小寫(xiě),和 Uppercase 用法是類(lèi)似的。
type Greeting = "Hello, world" type QuietGreeting = Lowercase<Greeting> // type QuietGreeting = "hello, world"
Capitalize<StringType>、Uncapitalize<StringType>
將字符中的首字母大寫(xiě)首字母小寫(xiě)大家一看也都懂,我們來(lái)寫(xiě)一個(gè)實(shí)現(xiàn)一個(gè)題目:下劃線命名轉(zhuǎn)小駝峰的命名方式。
比如: aa_bb_cc 轉(zhuǎn)換成 aaBbCc 。
實(shí)現(xiàn)思路:利用條件類(lèi)型進(jìn)行檢查,將字符串按照 _ 下劃線進(jìn)行分割,然后下劃線前后部分進(jìn)行首字母大寫(xiě),最后再將結(jié)果進(jìn)行首字母小寫(xiě)。
第一步:剔除下劃線,利用條件類(lèi)型提取 _ 下劃線分割的前后兩個(gè)部分,然后組合出新的字符串。
type RemoveBaseline<T extends string>T extends `${infer FirstStr}_${infer LastStr}` ? `${FirstStr}${LastStr}` : T; type NotBaselineStr = RemoveBaseline<"aa_bb"> // type NotBaselineStr = "aabb"
第二步:在第一步的基礎(chǔ)上將 FirstStr、LastStr 首字母大寫(xiě)進(jìn)行組合。
type UpperCamel<T extends string> T extends `${infer FirstStr}_${infer LastStr}` ? `${Capitalize<FirstStr>}${Capitalize<LastStr>}` : T; type Camelstr = UpperCamel<"aa_bb"> // type Camelstr = "AaBb"
第三步: 步驟二的結(jié)果只能實(shí)現(xiàn)第一個(gè)_下劃線的轉(zhuǎn)換,需要將字符串中所有的下劃線進(jìn)行轉(zhuǎn)換需要加上遞歸處理【分別_分開(kāi)的字符串進(jìn)行左右遞歸】,將所有的 _ 下劃線剔除。 最后將結(jié)果進(jìn)行首字母大寫(xiě),即可上菜。
type UpperCamel<T extends string> T extends `${infer FirstStr}_${infer LastStr}` ? `${Capitalize<UpperCamel<FirstStr>>}${Capitalize<UpperCamel<LastStr>>}` : T; type Camelstr = UpperCamel<"aa_bb_cc___dd"> //type Camelstr = "AaBbCcDd" // 再將 AaBbCcDd -> aaBbCcDd 這個(gè)方式就很簡(jiǎn)單了 直接調(diào)用一次 Uncapitalize type LowerCamel<T extends string> = Uncapitalize<UpperCamel<T>>; type lowerCamelStr = LowerCamel<"aa_bb_cc___dd"> // type lowerCamelStr = "aaBbCcDd"
至此,字符串的類(lèi)型操作以及模板字符串中的相關(guān)技術(shù)點(diǎn)就介紹完了。結(jié)合《TypeScript 玩轉(zhuǎn)類(lèi)型操作之基礎(chǔ)篇》我們體操的能力已經(jīng)具備,下一篇我們搞幾個(gè)面試中經(jīng)常被問(wèn)到的TS體操操作,練練手。歡迎大家評(píng)論區(qū)推薦一下。
參考資料
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Refer...
https://www.typescriptlang.org/docs/handbook/2/template-liter...
http://www.dbjr.com.cn/javascript/293208369.htm
以上就是TypeScript類(lèi)型操作之字符串處理功能詳解的詳細(xì)內(nèi)容,更多關(guān)于TypeScript類(lèi)型操作字符串處理的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- Typescript?extends?關(guān)鍵字繼承類(lèi)型約束及條件類(lèi)型判斷實(shí)現(xiàn)示例解析
- TypeScript中的數(shù)據(jù)類(lèi)型enum?type?interface基礎(chǔ)用法示例
- TypeScript中的聯(lián)合類(lèi)型使用示例詳解
- TypeScript 交叉類(lèi)型使用方法示例總結(jié)
- TypeScript類(lèi)型編程中的extends和infer示例解析
- TypeScript學(xué)習(xí)輕松玩轉(zhuǎn)類(lèi)型操作
- typescript?type類(lèi)型使用梳理總結(jié)
相關(guān)文章
xterm.js在web端實(shí)現(xiàn)Terminal示例詳解
這篇文章主要為大家介紹了xterm.js在web端實(shí)現(xiàn)Terminal示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-11-11TypeScript防抖節(jié)流函數(shù)示例詳解
這篇文章主要為大家介紹了TypeScript防抖節(jié)流函數(shù)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-08-08typescript難學(xué)嗎?前端有必要學(xué)?該怎么學(xué)typescript
TypeScript代碼與?JavaScript?代碼有非常高的兼容性,無(wú)門(mén)檻,你把?JS?代碼改為?TS?就可以運(yùn)行。TypeScript?應(yīng)該不會(huì)脫離?JavaScript?成為獨(dú)立的語(yǔ)言。學(xué)習(xí)?TypeScript?應(yīng)該主要指的是學(xué)習(xí)它的類(lèi)型系統(tǒng)。2022-12-12TypeScript類(lèi)型級(jí)別和值級(jí)別示例詳解
這篇文章主要為大家介紹了TypeScript類(lèi)型級(jí)別和值級(jí)別示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-02-02TypeScript數(shù)據(jù)結(jié)構(gòu)鏈表結(jié)構(gòu)?LinkedList教程及面試
這篇文章主要為大家介紹了TypeScript數(shù)據(jù)結(jié)構(gòu)鏈表結(jié)構(gòu)?LinkedList教程及面試,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-02-02TypeScript數(shù)據(jù)結(jié)構(gòu)之隊(duì)列結(jié)構(gòu)Queue教程示例
這篇文章主要為大家介紹了TypeScript數(shù)據(jù)結(jié)構(gòu)之隊(duì)列結(jié)構(gòu)Queue教程示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-02-02typescript快速上手的基礎(chǔ)知識(shí)篇
靜態(tài)類(lèi)型的typescript與傳統(tǒng)動(dòng)態(tài)弱類(lèi)型語(yǔ)言javascript不同,在執(zhí)行前會(huì)先編譯成javascript,因?yàn)樗鼜?qiáng)大的type類(lèi)型系統(tǒng)加持,能讓我們?cè)诰帉?xiě)代碼時(shí)增加更多嚴(yán)謹(jǐn)?shù)南拗啤W⒁?,它并不是一門(mén)全新的語(yǔ)言,所以并沒(méi)有增加額外的學(xué)習(xí)成本2022-12-12