這是一個艱難的。 TypeScript缺少mapped conditional types和一般recursive type definitions,這兩個都是我想用來給你的聯合類型。有你想要的一些癥結點:
- 一個
RouteEntry
的nested
性能有時會null
,並鍵入評估爲keyof null
或null[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!希望有所幫助。祝你好運!
一個小細節:如果您從RouteList中刪除索引簽名'readonly [key:string]:RouteEntry',這一切仍然有效。顯然,當你提供實際泛型參數爲'typeof list'時,索引簽名從不用於類型推斷。這使您可以在每個地方寫入'extends {}'而不是'extends RouteList',並且完全從答案中移除'RouteList'接口。 – artem