這個問題在這里已經有了答案: 打字稿:嵌套物件的深度鍵 10答案 1 小時前關閉。
語境
我正在嘗試創建一個型別安全的路徑段陣列以鉆入物件。我試圖構建型別的這個特定界面只有 2 個深度級別。我最終將使用這些段來使用點表示法對物件進行索引,但現在,我只是試圖確保型別受到足夠的約束,以便無法添加不正確的路徑。
例子
interface Days {
monday: string;
tueday: string;
wednesday: string;
thursday: string;
friday: string;
saturday: string;
sunday: string;
}
interface Weekend {
saturday: string;
sunday: string;
}
interface Example {
days: Days;
weekend: Weekend;
year: string;
}
type KeysOfUnions<T> = T extends T ? keyof T : never;
type ExamplePath<T extends keyof Example = keyof Example> = [T, KeysOfUnions<Example[T]>?];
const correctlyErrors: ExamplePath = ["days", "test"]; // good - this errors so we're catching bad paths
const allowsCorrectPath: ExamplePath = ["days", "monday"]; // good - valid paths are accepted
const allowsIncorrectPaths: ExamplePath = ["weekend", "monday"]; // bad! - invalid combinations of paths are allowed
到目前為止我提出的型別太松散了,允許路徑段的任何排列,即使這些是不可能的(即["weekend", "monday"])。我嘗試使用具有元組型別的泛型型別變數,方法是使用第一個路徑段作為型別T來索引Example型別,然后再獲取它的鍵。
這種索引方法的結果型別是以下的并集:
(Days | Weekend | string)
在此聯合型別上使用keyof,導致錯誤
Type 'string' is not assignable to type 'never'.ts(2322)
因此,相反,使用條件型別KeysOfUnions來獲取每個聯合成員的鍵,這導致您可以想象的過于松散的型別。
問題
如何使用第一個元素推斷元組的第二個元素(路徑段),確保型別系統強制只能添加路徑段的有效組合?
編輯 1:如果沒有更多的屬性可供鉆取,我也在尋找一種允許單段的解決方案。即["year"],理想情況下,向陣列添加更多元素會破壞型別。
編輯 2:一個可能不是那么小的附錄??給出的示例是一個具有 2 級嵌套的虛構界面,但是,我在我的問題中簡化了它的結構,實際界面大約有 5 級嵌套。例如,假設這些介面Days和Weekend示例介面更深,每天都包含子物件等等。我實際上打算提供一種解決方案來鍵入元組/陣列,僅針對 2 級屬性向下鉆取,忽略更深的路徑段。因此,對于這種約束,遞回方法可能是不可能的。
uj5u.com熱心網友回復:
當我們分解它時,這個問題變得非常簡單,而且你實際上非常接近;您只需要分別獲取每條路徑,而不是將它們全部放入一個元組中。在這里,我選擇使用映射型別(但您也可以使用分布式條件型別):
type KeyPaths<T> = {
[K in keyof T]: T[K] extends Record<any, any> ? [K, ...KeyPaths<T[K]>] : [K];
}[keyof T];
type ExamplePath = KeyPaths<Example>;
本質上,對于 的每個鍵T,我們檢查是否T[K]是一個物件,如果是,我們會進一步深入到該物件中。否則,我們只給[K].
它適用于給定的示例,并且還會給出有用的錯誤:
型別 '"test"' 不可分配給型別 '"monday" | “星期二” | “星期三” | “星期四” | “星期五” | “星期六” | “星期天” | 未定義'。(2322)
第二個:
型別“周末”不能分配給型別“天”。(2322)
操場
也可以為型別添加遞回限制:
type KeyPaths<T, Depth extends unknown[]> = Depth extends [] ? [] : {
[K in keyof T]: T[K] extends Record<any, any> ? [K, ...KeyPaths<T[K], Depth extends [...infer D, any] ? D : never>] : [K];
}[keyof T];
type ExamplePath = KeyPaths<Example, [0, 0]>;
在這里,我們只是使用元組的長度來跟蹤剩下的遞回,直到我們完成。
如果這對您沒有吸引力,因為它有點臟,您也可以使用數字,并索引到一個元組中以“減少”它們:
type Decrement<X extends number> = [-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9][X];
type KeyPaths<T, Depth extends number> = Decrement<Depth> extends -1 ? [] : {
[K in keyof T]: T[K] extends Record<any, any> ? [K, ...KeyPaths<T[K], Decrement<Depth>>] : [K];
}[keyof T];
type ExamplePath = KeyPaths<Example, 2>;
但是,這受到Decrement型別中包含的元素數量的限制。可以通過更多型別操作來消除此限制,但這超出了此問題的范圍且不必要。
uj5u.com熱心網友回復:
您想ExamplePath成為一個分布式物件型別(如ms/TS#47109中所創造的),您將型別分布在union[K, keyof Example[K]]中的每個物件上。它看起來像這樣:K keyof Example
type ExamplePath = { [K in keyof Example]: [K, keyof Example[K]] }[keyof Example]
/* type ExamplePath = ["days", keyof Days] | ["weekend", keyof Weekend] |
["year", number | typeof Symbol.iterator | "toString" | "charAt" |
"charCodeAt" | "concat" | ... 37 more ... | "padEnd"] */
這給了你你想要的"days"和"weekend"行為(不確定"year",因為第二個元素keyof string是所有string方法和明顯屬性的噩夢,但這顯然是你想要的,所以????)
無論如何,讓我們測驗一下:
const correctlyErrors: ExamplePath = ["days", "test"]; // error
const allowsCorrectPath: ExamplePath = ["days", "monday"]; // no error
const alsoErrors: ExamplePath = ["weekend", "monday"]; // error
看起來不錯。
Playground 代碼鏈接
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/508375.html
上一篇:如何使用TypeScript將多個道具傳遞給makeStyles()
下一篇:React/Typescript-使用ref時出現typescript錯誤-型別'(instance:HTMLInputElement|null)=>void'上不存在屬性&
