<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
最近在設計一些基礎的專案框架設計上的 sdk api,比如埋點系統、許可權系統之類的,要提供一些便捷的封裝方法給上層使用。於是遇到了這麼個場景。
有一個物件常數裡,存了一些方法,例如:
const METHODS = { a: () => "a" as const, b: () => "b" as const, c: () => "c" as const }
然後想要封裝這樣一個 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();
一切都簡簡單單,屬於是日常到不能再日常的程式碼,可是當我在 IDE 裡挪上去一看,這不對勁呀:
我預期這裡應該型別直接就是字串 a
了,怎麼會是個聯合型別?
我上上下下看了一遍型別推導,發現 keys.map(key => METHODS[key])
這一句裡,key 直接被推導成了 "a" | "b" | "c"
。
所以理所當然的結果也是推導成了 "a" | "b" | "c"
。
emmm......這還有些麻煩,先單獨寫個型別方法來推導結果試試,遞迴傳入的陣列泛型,取出每一次的 key
對應的 method
,再組合為陣列。
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
測試一下:
再將型別回到方法 useMethod
上帶入卻發現完全不行:
如果強行斷言 map
返回的結果,則直接會被推導為 never
型別
其實不難從程式碼裡看出,之所以無法推導原因有兩點,第一點是在 Typescript 編譯時這個階段,是無法推導這個函數泛型傳參的多種形態中的 key 是怎樣排序的,其次是在 map 方法中,key 值一直被推導成 "a" | "b" | "c"
導致。
所以如果我用元組作為泛型限定值,倒是可以實現:
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()
理解到這,我就思考雖然型別不能自動推匯出元組的組合排列方式,但是我卻可以寫一個方法來實現推導聯合型別生成元組。
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 時寫過的方法,這就派上用場了。
直接將 MethodKey
這個聯合型別解成元組之後限定泛型 T
,最後確實也可以成功推導結果。
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()
只是一個三行程式碼就實現的簡單方法,但要做出準確的結果推導卻需要這麼複雜的型別宣告去鋪墊,雖然最後寫出來很爽,但也感嘆作為庫開發者的一方真是非常不容易,這當中為了型別推導,還增加了冗餘的程式碼,為了支援元組的可選值,不得不將變數打為可選,從而需要先 filter
後 map
才能保證結果不會出現空值的型別推導。
身為一個前端,在寫 Ts 時時不時就要為幾個簡單結果的推導準確性花上小半天時間,有時候也覺得很不值得,不知道其他語言在型別上是否也有類似的煩惱,也希望 Typescript
團隊能有更好的型別推斷手段演進。
本文最後的解決方案不一定為最佳解決方案,不過作者也在社群和搜尋網站上檢索過答案,最後也沒找到滿意的解答,更多關於Typescript型別推導程式碼函數的資料請關注it145.com其它相關文章!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45