2017-08-30 130 views
5

我有一個遞歸類型的對象,我想獲取某個類型的任意子鍵的鍵。嵌套子對象的鍵

例如,下面我想獲得的聯合類型:

'/another' | '/parent' | '/child' 

例子:

export interface RouteEntry { 
    readonly name: string, 
    readonly nested : RouteList | null 
} 
export interface RouteList { 
    readonly [key : string] : RouteEntry 
} 

export const list : RouteList = { 
    '/parent': { 
     name: 'parentTitle', 
     nested: { 
      '/child': { 
       name: 'child', 
       nested: null, 
      }, 
     }, 
    }, 
    '/another': { 
     name: 'anotherTitle', 
     nested: null 
    }, 
} 

在打字稿,您可以使用keyof的typeof RouteList得到工會類型:

'/another' | '/parent' 

是否有方法也包括嵌套類型

回答

4

這是一個艱難的。 TypeScript缺少mapped conditional types和一般recursive type definitions,這兩個都是我想用來給你的聯合類型。有你想要的一些癥結點:

  • 一個RouteEntrynested性能有時會null,並鍵入評估爲keyof nullnull[keyof null]開始打破東西的表達。一個需要小心。我的解決方法包括添加一個虛擬鍵,使其不會爲空,然後在最後刪除它。
  • 無論您使用什麼類型的別名(稱爲RouteListNestedKeys<X>)似乎都需要根據自身定義,並且您將收到「循環引用」錯誤。一種解決方法是提供一些適用於某些有限嵌套級別的東西(例如,深度爲9級)。這可能會導致編譯器放慢速度,因爲它可能會急切地評估所有9個級別,而不是延遲評估,直到稍後。
  • 這需要大量的涉及映射類型的類型別名組合,並且有一個bug組合映射類型,直到TypeScript 2.6纔會被修復。 A workaround涉及使用通用默認類型參數。
  • 「在最後刪除虛擬鍵」步驟涉及一個稱爲Diff的類型操作,它至少需要TypeScript 2.4才能正常運行。

這意味着:我有一個解決方案,但我警告你,它很複雜和瘋狂。最後一件事之前,我把代碼:您需要更改

export const list: RouteList = { // ... 

export const list = { // ... 

也就是說,從list變量刪除類型標註。如果您將其指定爲RouteList,那麼您會丟棄TypeScript關於list確切結構的知識,並且您將只獲得string作爲關鍵類型。通過忽略註釋,你讓TypeScript推斷這個類型,因此它會記住整個嵌套結構。

好了,這裏有雲:

type EmptyRouteList = {[K in 'remove_this_value']: RouteEntry}; 
type ValueOf<T> = T[keyof T]; 
type Diff<T extends string, U extends string> = ({[K in T]: K} & 
    {[K in U]: never} & { [K: string]: never })[T]; 
type N0<X extends RouteList> = keyof X 
type N1<X extends RouteList, Y = {[K in keyof X]: N0<X[K]['nested'] & EmptyRouteList>}> = keyof X | ValueOf<Y> 
type N2<X extends RouteList, Y = {[K in keyof X]: N1<X[K]['nested'] & EmptyRouteList>}> = keyof X | ValueOf<Y> 
type N3<X extends RouteList, Y = {[K in keyof X]: N2<X[K]['nested'] & EmptyRouteList>}> = keyof X | ValueOf<Y> 
type N4<X extends RouteList, Y = {[K in keyof X]: N3<X[K]['nested'] & EmptyRouteList>}> = keyof X | ValueOf<Y> 
type N5<X extends RouteList, Y = {[K in keyof X]: N4<X[K]['nested'] & EmptyRouteList>}> = keyof X | ValueOf<Y> 
type N6<X extends RouteList, Y = {[K in keyof X]: N5<X[K]['nested'] & EmptyRouteList>}> = keyof X | ValueOf<Y> 
type N7<X extends RouteList, Y = {[K in keyof X]: N6<X[K]['nested'] & EmptyRouteList>}> = keyof X | ValueOf<Y> 
type N8<X extends RouteList, Y = {[K in keyof X]: N7<X[K]['nested'] & EmptyRouteList>}> = keyof X | ValueOf<Y> 
type N9<X extends RouteList, Y = {[K in keyof X]: N8<X[K]['nested'] & EmptyRouteList>}> = keyof X | ValueOf<Y> 
type RouteListNestedKeys<X extends RouteList, Y = Diff<N9<X>,'remove_this_value'>> = Y; 

讓我們嘗試一下:

export const list = { 
    '/parent': { 
     name: 'parentTitle', 
     nested: { 
      '/child': { 
       name: 'child', 
       nested: null, 
      }, 
     }, 
    }, 
    '/another': { 
     name: 'anotherTitle', 
     nested: null 
    }, 
} 

type ListNestedKeys = RouteListNestedKeys<typeof list> 

如果檢查ListNestedKeys,你會看到它是"parent" | "another" | "child",因爲你想要的。這取決於你是否值得。

Whe!希望有所幫助。祝你好運!

+0

一個小細節:如果您從RouteList中刪除索引簽名'readonly [key:string]:RouteEntry',這一切仍然有效。顯然,當你提供實際泛型參數爲'typeof list'時,索引簽名從不用於類型推斷。這使您可以在每個地方寫入'extends {}'而不是'extends RouteList',並且完全從答案中移除'RouteList'接口。 – artem