[英]Get union of all dotted object paths to properties of a particular type
我有一個 object 代表 dom 樹可見性
const visibilities = {
food: {
visible: true,
fruit: {
visible: true,
apple: {
visible: false
}
},
snack: {visible: false}
}
}
我想通過使用 util function 獲得蘋果的可見性
getVisibilities(visibilities, 'food.fruit.apple')
但我不知道如何輸入第二個參數。 當 visibilities[K] 是 {visibility: boolean} 類型時,我嘗試連接鍵
type VisibilityString<Prop extends {[key: string]: any}> = {[K in keyof Prop]: Prop[K]
extends {visible: boolean} ? K
extends string ? K | `${K}.${VisibilityArray<Prop[K]>}`: never : never}[keyof Prop]
得到 TypeError: VisibilityArray<Prop[K]> 不是字符串
將第二個參數更改為數組有效,但 typescript 在版本 4.4.4 中仍然給出錯誤
這些深度嵌套的遞歸條件類型通常有非常令人驚訝和不愉快的邊緣情況,並且在滿足您所有需求的類型與導致令人討厭的循環警告和編譯器速度變慢的類型之間可能只有一線之隔。 因此,雖然我在下面提出了一種適用於您的示例代碼的可能方法,但請注意,這很棘手,您很可能會遇到需要完全重構才能克服的問題。
無論如何,這是一種方法:
type _DKM<T, V> =
(T extends V ? "" : never) |
(T extends object ? { [K in Exclude<keyof T, symbol>]:
`${K}.${_DKM<T[K], V>}` }[Exclude<keyof T, symbol>] : never)
type TrimTrailingDot<T extends string> = T extends `${infer R}.` ? R : T;
type DeepKeysMatching<T, V> = TrimTrailingDot<_DKM<T, V>>
輔助類型_DKM<T, V>
代表DeepKeysMatching<T, V>
並完成大部分工作。 這個想法是它采用類型T
並且應該生成該類型中所有指向類型V
值的路徑的聯合。 我們會看到它實際上生成了這些路徑的並集,並在它們后面附加了一個尾隨點,因此我們需要在之后修剪這個點。
基本上:如果T
本身是V
類型,那么我們至少要返回空白路徑""
。 否則我們不返回never
。 如果T
不是 object 類型,我們就完成了; 否則我們 map _DKM<T[K], V>
遍歷鍵K
處的每個屬性,並在每個鍵K
和一個點之前加上它。 這給了我們想要的一切,除了尾隨的點。
因此TrimTrailingDot<T>
將從字符串中刪除尾隨點(如果有的話)。
最后DeepKeysMatching<T, V>
被定義為TrimTrailingDot<_DKM<T, V>>
,所以我們只是去掉那個點。
有了它,我們可以定義getVisibilities()
:
declare function getVisibilities<T>(visibilities: T,
path: DeepKeysMatching<T, { visible: boolean }> & {}
): boolean;
它在visibilities
參數的類型T
中是通用的,然后我們將path
參數限制為指向{visible: boolean}
類型屬性的T
路徑的並集,因此DeepKeysMatching<T, { visible: boolean }>
。
順便說一下, & {}
並沒有真正對類型做任何事情(空的 object 類型{}
匹配除undefined
和null
之外的所有內容,因此將一個string
與它相交最終將成為相同的字符串),但它確實給出了IntelliSense 提示我們希望它將path
類型顯示為字符串文字的聯合,而不是類型別名DeepKeysMatching<{...}, { visible: boolean }>
。 別名在某些情況下可能很有用,但您可能希望向調用者顯示特定的值列表。
讓我們測試一下:
const visibilities = {
food: {
visible: true,
fruit: {
visible: true,
apple: {
visible: false
}
},
snack: { visible: false }
}
}
getVisibilities(visibilities, "food.fruit.apple");
// function getVisibilities(
// visibilities: {...},
// path: "food.fruit.apple" | "food" | "food.fruit" | "food.snack"
// ): boolean
看起來挺好的。 當我們調用getVisibilities(visibilities, ...
時,系統會提示我們輸入類型為"food.fruit.apple" | "food" | "food.fruit" | "food.snack"
的path
參數。
getVisibilities(
{ a: 1, b: { c: { d: { visible: false } } } },
"b.c.d"
);
同樣看起來不錯,提示我們只能接受路徑"b.c.d"
。
所以我們完成了,它起作用了。 正如我之前所說,我確信有很多邊緣情況需要處理,但這至少回答了所提出的問題。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.