TS 類(lèi)型體操:圖解一個(gè)復(fù)雜高級(jí)類(lèi)型
之前我們零散地了解了一些 TypeScript 類(lèi)型體操的套路,但是沒(méi)有綜合練習(xí)下,今天就來(lái)做個(gè)高難度的體操,它會(huì)綜合運(yùn)用模式匹配、構(gòu)造、遞歸等套路,對(duì)提升類(lèi)型編程水平很有幫助。
我們要實(shí)現(xiàn)的高級(jí)類(lèi)型如下:
它的類(lèi)型參數(shù)是參數(shù)字符串 query string,會(huì)返回解析出的參數(shù)對(duì)象,如果有同名的參數(shù),會(huì)把值做合并。
先不著急實(shí)現(xiàn),我們先回顧下相關(guān)的類(lèi)型體操基礎(chǔ):
類(lèi)型體操基礎(chǔ)
模式匹配
模式匹配是指用一個(gè)類(lèi)型匹配一個(gè)模式類(lèi)型來(lái)提取其中的部分類(lèi)型到 infer 聲明的局部變量中。
比如提取 a=b 中的 a 和 b:
這種模式匹配的套路在數(shù)組、字符串、函數(shù)等類(lèi)型中都有很多應(yīng)用。
詳細(xì)了解可以看之前的一篇文章:模式匹配-讓你 ts 類(lèi)型體操水平暴增的套路
構(gòu)造
映射類(lèi)型用于生成索引類(lèi)型,生成的過(guò)程中可以對(duì)索引或者索引值做一些修改。
比如指定 key 和 value 來(lái)生成一個(gè)索引類(lèi)型:
詳細(xì)了解可以看之前的一篇文章:TS 類(lèi)型體操:索引類(lèi)型的映射再映射
遞歸
TypeScript 高級(jí)類(lèi)型支持遞歸,可以處理數(shù)量不確定的問(wèn)題。
比如不確定長(zhǎng)度的字符串的反轉(zhuǎn):
type ReverseStr<
Str extends string,
Result extends string = ''
> = Str extends `${infer First}${infer Rest}`
? ReverseStr<Rest, `${First}${Result}`>
: Result;
簡(jiǎn)單了解下模式匹配、構(gòu)造、遞歸都是什么之后,就可以開(kāi)始實(shí)現(xiàn)這個(gè)復(fù)雜的高級(jí)類(lèi)型 ParseQueryString 了:
思路分析
假設(shè)有這樣一個(gè) query string:a=1&a=2&b=3&c=4。
我們要首先把它分成 4 部分:也就是 a=1、a=2、b=3、c=4。這個(gè)就是用通過(guò)上面講的模式匹配來(lái)提取。
每一部分又可以進(jìn)一步處理,提取出 key value 構(gòu)造成索引類(lèi)型,比如 a=1 就可以通過(guò)模式匹配提取出 a、1,然后構(gòu)造成索引類(lèi)型 {a: 1}。
這樣就有了 4 個(gè)索引類(lèi)型 {a:1}、{a:2}、{b:3}、{c:4}。
結(jié)下來(lái)把它合并成一個(gè)就可以了,合并的時(shí)候如果有相同的 key 的值,要放到數(shù)組里。
就產(chǎn)生了最終的索引類(lèi)型:{a: [1,2], b: 3, c: 4}
整體流程是這樣的:
其中第一步并不知道有多少個(gè) a=1、b=2 這種 query param,所以要遞歸的做模式匹配來(lái)提取。
這就是這個(gè)高級(jí)類(lèi)型的實(shí)現(xiàn)思路。
下面我們具體來(lái)寫(xiě)一下:
代碼實(shí)現(xiàn)
我們按照上圖的順序來(lái)實(shí)現(xiàn),首先提取 query string 中的每一個(gè) query param:
query param 數(shù)量不確定,所以要用遞歸:
type ParseQueryString<Str extends string>
= Str extends `${infer Param}&${infer Rest}`
? MergeParams<ParseParam<Param>, ParseQueryString<Rest>>
: ParseParam<Str>;
類(lèi)型參數(shù) Str 為待處理的 query string。
通過(guò)模式匹配提取其中第一個(gè) query param 到 infer 聲明的局部變量 Param 中,剩余的字符串放到 Rest 中。
用 ParseParam 來(lái)處理 Param,剩余的遞歸處理,最后把它們合并到一起,也就是 MergeParams 。
如果模式匹配不滿足,說(shuō)明還剩下最后一個(gè) query param 了,也用 ParseParam 處理。
然后分別實(shí)現(xiàn)每一個(gè) query param 的 parse:
這個(gè)就是用模式匹配提取 key 和 value,然后構(gòu)造一個(gè)索引類(lèi)型:
type ParseParam<Param extends string>
= Param extends `${infer Key}=${infer Value}`
? { [K in Key]: Value }
: {};
這里構(gòu)造索引類(lèi)型用的就是映射類(lèi)型的語(yǔ)法。
先來(lái)測(cè)試下這個(gè) ParseParam:
做完每一個(gè) query param 的解析了,之后把它們合并到一起就行:
合并的部分就是 MergeParams:
type MergeParams<
OneParam extends object,
OtherParam extends object
> = {
[Key in keyof OneParam | keyof OtherParam]:
Key extends keyof OneParam
? Key extends keyof OtherParam
? MergeValues<OneParam[Key], OtherParam[Key]>
: OneParam[Key]
: Key extends keyof OtherParam
? OtherParam[Key]
: never
}
兩個(gè)索引類(lèi)型的合并也是要用映射類(lèi)型的語(yǔ)法構(gòu)造一個(gè)新的索引類(lèi)型。
key 是取自兩者也就是 key in keyof OneParam | keyof OtherParam。
value 要分兩種情況:
- 如果兩個(gè)索引類(lèi)型都有的 key,就要做合并,也就是 MergeValues。
- 如果只有其中一個(gè)索引類(lèi)型有,那就取它的值,也就是 OtherParam[key] 或者 OneParam[Key]。
合并的時(shí)候,如果兩者一樣就返回任意一個(gè),如果不一樣,就合并到數(shù)組里返回,也就是 [One, Other]。如果本來(lái)是數(shù)組的話,那就是數(shù)組的合并 [One, ...Other]。
type MergeValues<One, Other> =
One extends Other
? One
: Other extends unknown[]
? [One, ...Other]
: [One, Other];
測(cè)試下 MergeValues:
這樣,我們就實(shí)現(xiàn)了整個(gè)高級(jí)類(lèi)型,整體測(cè)試下:
這個(gè)案例綜合運(yùn)用到了遞歸、模式提取、構(gòu)造的套路,還是比較復(fù)雜的。
可以對(duì)照著這張圖來(lái)看下完整代碼:
type ParseParam<Param extends string> =
Param extends `${infer Key}=${infer Value}`
? {
[K in Key]: Value
} : {};
type MergeValues<One, Other> =
One extends Other
? One
: Other extends unknown[]
? [One, ...Other]
: [One, Other];
type MergeParams<
OneParam extends object,
OtherParam extends object
> = {
[Key in keyof OneParam | keyof OtherParam]:
Key extends keyof OneParam
? Key extends keyof OtherParam
? MergeValues<OneParam[Key], OtherParam[Key]>
: OneParam[Key]
: Key extends keyof OtherParam
? OtherParam[Key]
: never
}
type ParseQueryString<Str extends string> =
Str extends `${infer Param}&${infer Rest}`
? MergeParams<ParseParam<Param>, ParseQueryString<Rest>>
: ParseParam<Str>;
type ParseQueryStringResult = ParseQueryString<'a=1&a=2&b=2&c=3'>;
總結(jié)
我們首先復(fù)習(xí)了下 3 種類(lèi)型體操的套路:
模式匹配:一個(gè)類(lèi)型匹配一個(gè)模式類(lèi)型,提取其中的部分類(lèi)型到 infer 聲明的局部變量中
構(gòu)造:通過(guò)映射類(lèi)型的語(yǔ)法來(lái)構(gòu)造新的索引類(lèi)型,構(gòu)造過(guò)程中可以對(duì)索引和值做一些修改
遞歸:當(dāng)處理數(shù)量不確定的類(lèi)型時(shí),可以每次只處理一個(gè),剩下的遞歸來(lái)做
然后用這些套路來(lái)實(shí)現(xiàn)了一個(gè) ParseQueryString 的復(fù)雜高級(jí)類(lèi)型。
如果能獨(dú)立實(shí)現(xiàn)這個(gè)高級(jí)類(lèi)型,說(shuō)明你對(duì)這三種類(lèi)型體操的套路掌握的就挺不錯(cuò)的了。