TypeScript 類型編程之索引類型遞歸去掉可選修飾
前言
這兩天東東遇到一個(gè) TS 的問(wèn)題,跑來(lái)問(wèn)我。
問(wèn)題是這樣的:
這樣一個(gè) interface,想取出 userInfo 的類型來(lái):
interface Result{ data?: { userInfo?: { name: string; } } }
他是這樣取的:
type userInfo = Result['data']['userInfo'];
但是會(huì)報(bào)錯(cuò):
說(shuō)是 userInfo 不在這個(gè)聯(lián)合類型上。
這很正常,因?yàn)榭蛇x索引的含義就是值和 undefined 的聯(lián)合類型 value | undefined。
于是他問(wèn)我應(yīng)該怎么取?
我和他說(shuō)這個(gè)問(wèn)題有兩種不同復(fù)雜度的解決方案,有簡(jiǎn)單的有復(fù)雜的,問(wèn)他想聽(tīng)哪個(gè)。
他說(shuō)想聽(tīng)簡(jiǎn)單的,于是我告訴他這樣寫(xiě):
type userInfo = Required<Required<Result>['data']>['userInfo']
Required 是 ts 內(nèi)置的高級(jí)類型,是把索引類型的所有可選修飾去掉的。
所以每一層用 Required 處理一下再取索引的值就可以了。
但是這樣雖然簡(jiǎn)單,當(dāng)取的層數(shù)多了要寫(xiě)很多次 Required,也挺麻煩的。
然后東東又問(wèn)我如果是復(fù)雜的那個(gè),要怎么寫(xiě)?
我和他說(shuō)復(fù)雜的那個(gè)寫(xiě)起來(lái)麻煩一些,但好處是用起來(lái)簡(jiǎn)單,不管多少層都只需要處理一次:
首先要知道 Required 是怎么實(shí)現(xiàn)的:
他這里用到了映射類型的語(yǔ)法,作用是對(duì)索引類型做一些修改,生成新的索引類型。
P in keyof T 就是遍歷索引類型 T 中的所有索引 P,用來(lái)構(gòu)造新的索引類型,值保持不變,也就是 T[P]。
構(gòu)造的過(guò)程中可以加上可選的修飾、也可以去掉可選的修飾,還可以對(duì)值和索引做一些修改。
所以和 Required 相對(duì)的 Partial 就是這樣實(shí)現(xiàn)的:
我們想一次處理完所有層級(jí),都把可選的修飾給去掉,那就要遞歸處理,也就是這樣:
type DeepRequired<Obj> = { [Key in keyof Obj]-?: IsOptional<Key, Obj> extends never? Obj[Key]: DeepRequired<Obj[Key]> }
遍歷索引類型 Obj 中的所有索引 Key,通過(guò) -? 去掉可選,然后對(duì)值要做一下判斷,如果還是可選索引,那就遞歸處理。
那怎么實(shí)現(xiàn)這個(gè) IsOptional 的判斷索引是否是可選的高級(jí)類型呢?
判斷某個(gè)類型要根據(jù)他的性質(zhì)來(lái),可選的性質(zhì)就是 value | undefined,也就是說(shuō)可能是空。
可以這樣來(lái)實(shí)現(xiàn)可選的判斷:
type IsOptional<Key extends keyof Obj, Obj> = {} extends Pick<Obj, Key> ? Key : never;
Obj 是索引類型,Key 是他的某個(gè)索引,因?yàn)榭蛇x索引的性質(zhì)是可能為空,所以 {} 就可能是索引類型的子類型。
這里的 Pick 也是內(nèi)置的高級(jí)類型,作用是取出一部分索引構(gòu)造新的索引類型:
同樣是通過(guò)映射類型的語(yǔ)法實(shí)現(xiàn)的:
這里 a 可能是沒(méi)有的,那當(dāng)沒(méi)有的時(shí)候不就是 {} 么? 所以可以用 {} extends Pick<Obj, Key> 來(lái)判斷是不是可選索引。
綜上,遞歸去掉索引類型的可選修飾就是這樣實(shí)現(xiàn)的:
type IsOptional<Key extends keyof Obj, Obj> = {} extends Pick<Obj, Key> ? Key : never; type DeepRequired<Obj> = { [Key in keyof Obj]-?: IsOptional<Key, Obj> extends never? Obj[Key]: DeepRequired<Obj[Key]> }
我們測(cè)試一下:
現(xiàn)在只要處理一次,就可以取任意層級(jí)的索引值了,方便了很多。
其實(shí)寫(xiě)成這樣就可以用了,但是有時(shí)候你會(huì)遇到這樣的問(wèn)題:
TS 沒(méi)有把最終的結(jié)果計(jì)算出來(lái)。
這個(gè)是 TS 的機(jī)制,默認(rèn)是懶計(jì)算的,用不到的不會(huì)計(jì)算。
那怎么讓他計(jì)算出最終結(jié)果呢?
加上一段邏輯觸發(fā)計(jì)算就可以了,比如 xxx extends any 這種肯定會(huì)成立的條件判斷:
再測(cè)試一下你就會(huì)發(fā)現(xiàn) TS 計(jì)算出了最終的結(jié)果:
總結(jié)
想取一個(gè)可選索引的值,需要先用 Required 把索引類型去掉可選然后再取。但是當(dāng)層數(shù)多了的話,這樣一層層處理挺麻煩的,可以用類型編程遞歸處理下。
用映射類型的語(yǔ)法去掉索引類型的可選修飾,判斷值的類型,如果還是可選的索引,那就繼續(xù)遞歸的處理。
判斷可選索引是通過(guò)可選的性質(zhì)來(lái)的,可選索引的值是 value | undefined, 所以 {} extends Pick<Obj, Key> 成立的話就代表這個(gè) Key 是可選的。
可能會(huì)遇到類型沒(méi)有全部計(jì)算的問(wèn)題,這是 TS 的機(jī)制,默認(rèn)是懶計(jì)算的,可以加上 xx extends any 這種不影響結(jié)果的條件類型來(lái)觸發(fā)計(jì)算。
層層用 Required 處理在層數(shù)少的情況下比較簡(jiǎn)單,但層數(shù)多了的時(shí)候還是遞歸處理更方便一些,而且這樣的高級(jí)類型是可以復(fù)用的,可以用在別的地方,這也是類型編程的好處。
到此這篇關(guān)于TypeScript 類型編程之索引類型遞歸去掉可選修飾的文章就介紹到這了,更多相關(guān)TypeScript 索引類型遞歸內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
一個(gè)可以得到元素真實(shí)的背景顏色的javascript腳本
一個(gè)可以得到元素真實(shí)的背景顏色的javascript腳本...2007-07-07js+HTML5 canvas 實(shí)現(xiàn)簡(jiǎn)單的加載條(進(jìn)度條)功能示例
這篇文章主要介紹了js+HTML5 canvas 實(shí)現(xiàn)簡(jiǎn)單的加載條(進(jìn)度條)功能,涉及javascript使用時(shí)間函數(shù)與canvas繪圖結(jié)合實(shí)現(xiàn)進(jìn)度條的相關(guān)操作技巧,需要的朋友可以參考下2019-07-07Javascript將數(shù)字轉(zhuǎn)化成為貨幣格式字符串
這篇文章主要介紹Javascript將數(shù)字轉(zhuǎn)化成為貨幣格式字符串的方法,通俗易懂,需要的朋友可以參考下。2016-06-06JavaScript實(shí)現(xiàn)無(wú)窮滾動(dòng)加載數(shù)據(jù)
這篇文章主要為大家詳細(xì)介紹了JavaScript實(shí)現(xiàn)無(wú)窮滾動(dòng)加載數(shù)據(jù),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-05-05在JavaScript中添加css樣式(js追加類)代碼示例
這篇文章主要給大家介紹了關(guān)于在JavaScript中如何添加css樣式,也就是js追加類的相關(guān)資料,JavaScript是一種廣泛應(yīng)用于互聯(lián)網(wǎng)開(kāi)發(fā)的編程語(yǔ)言,它能夠幫助網(wǎng)頁(yè)實(shí)現(xiàn)動(dòng)態(tài)效果和交互性,需要的朋友可以參考下2024-01-01采用自執(zhí)行的匿名函數(shù)解決for循環(huán)使用閉包的問(wèn)題
這篇文章主要介紹了采用自執(zhí)行的匿名函數(shù)解決for循環(huán)使用閉包的問(wèn)題,很簡(jiǎn)單,但比較實(shí)用,需要的朋友可以參考下2014-09-09