簡(jiǎn)單三行代碼函數(shù)實(shí)現(xiàn)幾十行Typescript類型推導(dǎo)
場(chǎng)景
最近在設(shè)計(jì)一些基礎(chǔ)的項(xiàng)目框架設(shè)計(jì)上的 sdk api,比如埋點(diǎn)系統(tǒng)、權(quán)限系統(tǒng)之類的,要提供一些便捷的封裝方法給上層使用。于是遇到了這么個(gè)場(chǎng)景。
有一個(gè)對(duì)象常量里,存了一些方法,例如:
const METHODS = { a: () => "a" as const, b: () => "b" as const, c: () => "c" as const }
然后想要封裝這樣一個(gè) hook 例如 useMethod
給上層的 React 上下文使用:
type MethodKey = keyof typeof METHODS function useMethods(keys: MethodKey[]) { return keys.map(key => METHODS[key]) } // case const [a, b] = useMethods(['a', 'b']) // expect to "a" a();
一切都簡(jiǎn)簡(jiǎn)單單,屬于是日常到不能再日常的代碼,可是當(dāng)我在 IDE 里挪上去一看,這不對(duì)勁呀:
我預(yù)期這里應(yīng)該類型直接就是字符串 a
了,怎么會(huì)是個(gè)聯(lián)合類型?
摸魚吃瓜式排查
我上上下下看了一遍類型推導(dǎo),發(fā)現(xiàn) keys.map(key => METHODS[key])
這一句里,key 直接被推導(dǎo)成了 "a" | "b" | "c"
。
所以理所當(dāng)然的結(jié)果也是推導(dǎo)成了 "a" | "b" | "c"
。
emmm......這還有些麻煩,先單獨(dú)寫個(gè)類型方法來推導(dǎo)結(jié)果試試,遞歸傳入的數(shù)組泛型,取出每一次的 key
對(duì)應(yīng)的 method
,再組合為數(shù)組。
type MethodValue<K extends MethodKey> = typeof METHODS[K] type GetMethodValue<T extends MethodKey[]> = T extends [] ? [] : T extends [infer F extends MethodKey, ...infer Rest extends MethodKey[]] ? [MethodValue<F>, ...GetMethodValue<Rest>] : never
測(cè)試一下:
再將類型回到方法 useMethod
上帶入?yún)s發(fā)現(xiàn)完全不行:
如果強(qiáng)行斷言 map
返回的結(jié)果,則直接會(huì)被推導(dǎo)為 never
類型
元組大法
其實(shí)不難從代碼里看出,之所以無法推導(dǎo)原因有兩點(diǎn),第一點(diǎn)是在 Typescript 編譯時(shí)這個(gè)階段,是無法推導(dǎo)這個(gè)函數(shù)泛型傳參的多種形態(tài)中的 key 是怎樣排序的,其次是在 map 方法中,key 值一直被推導(dǎo)成 "a" | "b" | "c"
導(dǎo)致。
所以如果我用元組作為泛型限定值,倒是可以實(shí)現(xiàn):
type GetMethodValue<T extends (MethodKey | void)[]> = T extends [] ? [] : T extends [infer F extends MethodKey, ...infer Rest extends MethodKey[]] ? [MethodValue<F>, ...GetMethodValue<Rest>] : never function useMethods<T extends ['a'?, 'b'?, 'c'?]>(keys: T) { return keys.filter((key): key is MethodKey => !!key).map((key) => METHODS[key]) as GetMethodValue<T> } const [a, b] = useMethods(['a', 'b']) const valueA = a()
理解到這,我就思考雖然類型不能自動(dòng)推導(dǎo)出元組的組合排列方式,但是我卻可以寫一個(gè)方法來實(shí)現(xiàn)推導(dǎo)聯(lián)合類型生成元組。
type Permutation<T, U = T> = [T] extends [never] ? [] : U extends T ? [U, ...Permutation<Exclude<T, U>>] : never; // expect to ['a', 'b'] | ['b', 'a'] type value = Permutation<'a' | 'b'>
這是我之前在寫 TypeChallenge 時(shí)寫過的方法,這就派上用場(chǎng)了。
直接將 MethodKey
這個(gè)聯(lián)合類型解成元組之后限定泛型 T
,最后確實(shí)也可以成功推導(dǎo)結(jié)果。
type Permutation<T, U = T> = [T] extends [never] ? [] : U extends T ? [U?, ...Permutation<Exclude<T, U>>] : never; type MethodKey = keyof typeof METHODS type MethodValue<K extends MethodKey> = typeof METHODS[K] type GetMethodValue<T extends (MethodKey | void)[]> = T extends [] ? [] : T extends [infer F extends MethodKey, ...infer Rest extends MethodKey[]] ? [MethodValue<F>, ...GetMethodValue<Rest>] : never const METHODS = { a: () => "a" as const, b: () => "b" as const, c: () => "c" as const } function useMethods<T extends Permutation<MethodKey>>(keys: T) { return keys.filter((key): key is MethodKey => !!key) .map((key) => METHODS[key]) as GetMethodValue<T> } const [a, b] = useMethods(['a', 'b']) const valueA = a()
感嘆
只是一個(gè)三行代碼就實(shí)現(xiàn)的簡(jiǎn)單方法,但要做出準(zhǔn)確的結(jié)果推導(dǎo)卻需要這么復(fù)雜的類型聲明去鋪墊,雖然最后寫出來很爽,但也感嘆作為庫開發(fā)者的一方真是非常不容易,這當(dāng)中為了類型推導(dǎo),還增加了冗余的代碼,為了支持元組的可選值,不得不將變量打?yàn)榭蛇x,從而需要先 filter
后 map
才能保證結(jié)果不會(huì)出現(xiàn)空值的類型推導(dǎo)。
身為一個(gè)前端,在寫 Ts 時(shí)時(shí)不時(shí)就要為幾個(gè)簡(jiǎn)單結(jié)果的推導(dǎo)準(zhǔn)確性花上小半天時(shí)間,有時(shí)候也覺得很不值得,不知道其他語言在類型上是否也有類似的煩惱,也希望 Typescript
團(tuán)隊(duì)能有更好的類型推斷手段演進(jìn)。
本文最后的解決方案不一定為最佳解決方案,不過作者也在社區(qū)和搜索網(wǎng)站上檢索過答案,最后也沒找到滿意的解答,更多關(guān)于Typescript類型推導(dǎo)代碼函數(shù)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
typescript難學(xué)嗎?前端有必要學(xué)?該怎么學(xué)typescript
TypeScript代碼與?JavaScript?代碼有非常高的兼容性,無門檻,你把?JS?代碼改為?TS?就可以運(yùn)行。TypeScript?應(yīng)該不會(huì)脫離?JavaScript?成為獨(dú)立的語言。學(xué)習(xí)?TypeScript?應(yīng)該主要指的是學(xué)習(xí)它的類型系統(tǒng)。2022-12-12TypeScript類型any never void和unknown使用場(chǎng)景區(qū)別
這篇文章主要為大家介紹了TypeScript類型any never void和unknown使用場(chǎng)景區(qū)別,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-10-10typescript快速上手的進(jìn)階類型與技術(shù)
本文講述了typescript開發(fā)的一些高級(jí)的類型與技術(shù),算是對(duì)于基礎(chǔ)知識(shí)點(diǎn)的補(bǔ)充,具體內(nèi)容包括:比如元組、枚舉類、接口、泛型相關(guān)概念等。雖說是進(jìn)階,但是內(nèi)容不算多也并不難理解。2022-12-12postman數(shù)據(jù)加解密實(shí)現(xiàn)APP登入接口模擬請(qǐng)求
對(duì)于Postman的使用,一般情況下只要發(fā)發(fā)確定的請(qǐng)求與參數(shù)就可以的了,然而,在使用的時(shí)候,尤其是接口測(cè)試時(shí),請(qǐng)求接口的設(shè)計(jì)里面都有數(shù)據(jù)加密,參數(shù)驗(yàn)簽,返回?cái)?shù)據(jù)也有進(jìn)行加密的,這個(gè)時(shí)候就需要使用一些腳本做處理,模擬app登入請(qǐng)求的操作2021-08-08ts?類型體操?Chainable?Options?可鏈?zhǔn)竭x項(xiàng)示例詳解
這篇文章主要為大家介紹了ts?類型體操?Chainable?Options?可鏈?zhǔn)竭x項(xiàng)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-09-09TypeScript十大排序算法插入排序?qū)崿F(xiàn)示例詳解
這篇文章主要為大家介紹了TypeScript十大排序算法插入排序?qū)崿F(xiàn)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-02-02TypeScript手寫一個(gè)簡(jiǎn)單的eslint插件實(shí)例
這篇文章主要為大家介紹了TypeScript手寫一個(gè)簡(jiǎn)單的eslint插件實(shí)例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-02-02typescript快速上手的基礎(chǔ)知識(shí)篇
靜態(tài)類型的typescript與傳統(tǒng)動(dòng)態(tài)弱類型語言javascript不同,在執(zhí)行前會(huì)先編譯成javascript,因?yàn)樗鼜?qiáng)大的type類型系統(tǒng)加持,能讓我們?cè)诰帉懘a時(shí)增加更多嚴(yán)謹(jǐn)?shù)南拗啤W⒁?,它并不是一門全新的語言,所以并沒有增加額外的學(xué)習(xí)成本2022-12-12