TypeScript類型操作之字符串處理功能詳解
引言
本文主要介紹模板 TypeScript 對模板字符串的能力支持及實現(xiàn)原理,深入介紹了 TypeScript 提供的字符串操作能力。結合《TypeScript玩轉類型操作之基礎篇》內(nèi)容,完整覆蓋了TS體操的能力基礎。附帶一步步實現(xiàn)將 aa_bb___cc 轉換成 aaBbCc 的類型實現(xiàn)技巧。
模板字符串
模板字符串是 JS 語言提供了使用反引號 ` 分割的字面量,支持多行字符、嵌入字符串插值表達式以及附帶標簽(與標簽函數(shù)異同使用)。
let str = `user name is : ${name}`; // 插值表達式 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中的應用不是本文我們的重點,如果大家想深入了解一下可以先看看MDN然后再翻翻ECMAScript的規(guī)范基本上就很深入了。主要是介紹在 TypeScript 中,支持了哪些特性,提供了什么能力。
TS 中模板字符串的相關特性
當模板字符串插值表達式用于類型位置時,具有以下特性:計算出來的模板字符串值會作為一個字符串固定類型、如果插值表達式是一個聯(lián)合類型那么將得到一個新的聯(lián)合類型成員是插值聯(lián)合類型成員與模板字符串靜態(tài)部分生成字符串并集。
構造新的類型
普通插值表達式,構造一個復合型的字符串類型。
type World = `world`; type Greeting = `hello ${World}`; // type Greeting = "hello world";
在插值位置使用聯(lián)合類型時,構造出一個新的字符串聯(lián)合類型。
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 = | "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 會動態(tài)遍歷插值中的聯(lián)合類型,然后構造出所有情況下組合出來的并集聯(lián)合類型。這在我們項目中組合出新的類型挺實用。
使用模板字符串進行推理
基于對象類型內(nèi)部屬性名構造字符串類型,然后根據(jù)對象類型中對應的屬性值類型進行推導新的類型。功能會很強大。我們以一個案例進行解釋:題目是對一個對象進行代理,代理后需要監(jiān)聽到這個對象的變化事件,現(xiàn)在需要定義一套類型來實現(xià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)在我們需要使用類型來對 調用 on 上注冊的事件名稱做檢查:要求on監(jiān)聽的屬性名稱必須滿足(屬性名+ 'Changed' ) 格式,監(jiān)聽函數(shù)返回值類型和原對象一致【監(jiān)聽name,返回值類型是 string、監(jiān)聽age返回值類型是number】。我們分三步實現(xiàn)一下這個類型的定義。
第一步:如何修飾 makeWatchedObject 函數(shù)的類型,定義其返回值類型為傳入的值類型。
declare function makeWatchedObject<Type>(obj: Type): Type & { on(eventName: string, callback:(newValue: any)=>void):void };
第二步:定義一個類型工具,負責提取類型上的屬性作為模板字符串的插值。大家不清楚 extends 、 keyof 用法的請看這篇文章 《TypeScript 玩轉類型操作之基礎篇》,里面詳細介紹了其使用場景以及方式,包含與 typeof、infer 等關鍵詞的配合使用以及Omit等高級類型的源碼解析。
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ù)的修飾, 并提取目標類型上的值類型作為callback參數(shù)的類型。
// 定義一個工具類型,特殊處理定義一個 on 函數(shù)的類型,主要是修飾 on 的參數(shù)類型 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 在編譯過程中,編譯器會將代碼解析成 AST (抽象語法樹)。對于模板字符串編譯器會識別出模板字符串的靜態(tài)部分(普通的字符文本)和動態(tài)部分(插值表達式)解析的結果仍然是AST結構,完成解析以后TypeScript 會進行類型推導。類型推導時編譯器會分析每個插值表達式的類型,根據(jù)類型不同進行轉換推導出整個模板字符串的類型。這里會有類似于 JavaScript 類型轉換的場景非字符如何 toPrimitive 。
TS代碼:
const name = 'Alice'; const age = 30; const message = `Hello, my name is ${name} and I am ${age} years old.`;
解析成類似AST語法樹:
TemplateLiteral └── Quasi (Hello, my name is ) └── Expression (name) └── Quasi ( and I am ) └── Expression (age) └── Quasi ( years old.)
解析模板字符串的結構,然后根據(jù)插值表達式的類型推導出整個模板字符串的類型。
字符串操作工具類型【有的說是高級類型】
TypeScript 為了提升對字符串的操作能力(大小寫轉換、首字母大小寫轉換),內(nèi)置封裝了四個主要的工具類型。分別是 Uppercase<StringType>、Lowercase<StringType>、Capitalize<StringType>、Uncapitalize<StringType> 這些類型并不是像 Omit、Record 等高級類型在 type.d.ts 中自定義封裝的,Typescript 為了性能將其內(nèi)置在編譯器中實現(xiàn)【源碼實在沒看明白,抱歉了諸位】。
我們來分別看看他們的功能:
Uppercase<StringType>
將字符串中的每個字母都轉換成大寫。
type Greeting = "Hello, world" type ShoutyGreeting = Uppercase<Greeting> //type ShoutyGreeting = "HELLO, WORLD"
我們來實現(xiàn)這樣一個簡單的題目:將一個對象上的所有屬性名提取出來,并轉成大寫。
// 限制一下K從T的屬性中提取,并且是string類型 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" // 如果你想只有一個入口,也可以這樣再封一個自定義的類型工具 type A<T extends { [k: string ]: any }> = PickPropertyToUppercase<T, keyof T>; type APerType = A<typeof per> //type APerType = "NAME" | "AGE"
這些基礎能力結合我們上一篇文章中的類型基礎【keyof、 typeof、infer、extends、條件類型~】就是我們玩轉體操的基礎啦~
Lowercase<StringType>
將字符串中所有的字母都變成小寫,和 Uppercase 用法是類似的。
type Greeting = "Hello, world" type QuietGreeting = Lowercase<Greeting> // type QuietGreeting = "hello, world"
Capitalize<StringType>、Uncapitalize<StringType>
將字符中的首字母大寫首字母小寫大家一看也都懂,我們來寫一個實現(xiàn)一個題目:下劃線命名轉小駝峰的命名方式。
比如: aa_bb_cc 轉換成 aaBbCc 。
實現(xiàn)思路:利用條件類型進行檢查,將字符串按照 _ 下劃線進行分割,然后下劃線前后部分進行首字母大寫,最后再將結果進行首字母小寫。
第一步:剔除下劃線,利用條件類型提取 _ 下劃線分割的前后兩個部分,然后組合出新的字符串。
type RemoveBaseline<T extends string>T extends `${infer FirstStr}_${infer LastStr}` ? `${FirstStr}${LastStr}` : T; type NotBaselineStr = RemoveBaseline<"aa_bb"> // type NotBaselineStr = "aabb"
第二步:在第一步的基礎上將 FirstStr、LastStr 首字母大寫進行組合。
type UpperCamel<T extends string> T extends `${infer FirstStr}_${infer LastStr}` ? `${Capitalize<FirstStr>}${Capitalize<LastStr>}` : T; type Camelstr = UpperCamel<"aa_bb"> // type Camelstr = "AaBb"
第三步: 步驟二的結果只能實現(xiàn)第一個_下劃線的轉換,需要將字符串中所有的下劃線進行轉換需要加上遞歸處理【分別_分開的字符串進行左右遞歸】,將所有的 _ 下劃線剔除。 最后將結果進行首字母大寫,即可上菜。
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 這個方式就很簡單了 直接調用一次 Uncapitalize type LowerCamel<T extends string> = Uncapitalize<UpperCamel<T>>; type lowerCamelStr = LowerCamel<"aa_bb_cc___dd"> // type lowerCamelStr = "aaBbCcDd"
至此,字符串的類型操作以及模板字符串中的相關技術點就介紹完了。結合《TypeScript 玩轉類型操作之基礎篇》我們體操的能力已經(jīng)具備,下一篇我們搞幾個面試中經(jīng)常被問到的TS體操操作,練練手。歡迎大家評論區(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類型操作之字符串處理功能詳解的詳細內(nèi)容,更多關于TypeScript類型操作字符串處理的資料請關注腳本之家其它相關文章!
相關文章
xterm.js在web端實現(xiàn)Terminal示例詳解
這篇文章主要為大家介紹了xterm.js在web端實現(xiàn)Terminal示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-11-11TypeScript防抖節(jié)流函數(shù)示例詳解
這篇文章主要為大家介紹了TypeScript防抖節(jié)流函數(shù)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-08-08typescript難學嗎?前端有必要學?該怎么學typescript
TypeScript代碼與?JavaScript?代碼有非常高的兼容性,無門檻,你把?JS?代碼改為?TS?就可以運行。TypeScript?應該不會脫離?JavaScript?成為獨立的語言。學習?TypeScript?應該主要指的是學習它的類型系統(tǒng)。2022-12-12TypeScript數(shù)據(jù)結構鏈表結構?LinkedList教程及面試
這篇文章主要為大家介紹了TypeScript數(shù)據(jù)結構鏈表結構?LinkedList教程及面試,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-02-02TypeScript數(shù)據(jù)結構之隊列結構Queue教程示例
這篇文章主要為大家介紹了TypeScript數(shù)據(jù)結構之隊列結構Queue教程示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-02-02