[英]Typescript: Define type as union of types with a partially present object
假設我有兩種這樣定義的類型:
const type A = { identifier: string, properties: { p1: string, p2: string }};
const type B = { identifier: string, properties: { p3: string, p4: string }};
我想將一個類型定義為 A 和 B 的聯合,並且“屬性”鍵將被部分填充(但始終存在,即至少應該存在一個鍵)。 但是,我不想在這兩種類型的“屬性”object 的鍵之間混合。
例如,這些實例將是有效的:
{ identifier: 'id', properties: { p1: 'prop1', p2: 'prop2' }}
{ identifier: 'id', properties: { p2: 'prop2' }}
{ identifier: 'id', properties: { p3: 'prop3', p4: 'prop4' }}
{ identifier: 'id', properties: { p3: 'prop3' }}
但這些實例無效:
{ identifier: 'id', properties: {}} // 'properties' is empty
{ identifier: 'id' } // 'properties' is missing
{ identifier: 'id', properties: { p1: 'prop1', p3: 'prop3' }} // 'properties' has keys from A and B
我嘗試了很多不同的配置,但似乎沒有任何效果。 另外,我沒有找到任何問題來回答這個問題。
這看起來像是以下兩個問題的組合:
“ 如何創建需要設置單個屬性的 Partial-like ” 創建一個類型 function AtLeastOneProp<T>
,它輪流將 object 類型T
轉換為Partial<T>
-like 類型,但它不允許所有屬性都是失蹤:
type AtLeastOneProp<T extends object> = T extends object ? { [K in keyof T]-?:
Pick<T, K> & Partial<T> extends infer O ? { [P in keyof O]: O[P] } : never }[keyof T]
: never;
在這里,我使用分布式條件類型、 條件類型推斷以及各種實用程序和映射類型來迭代T
的每個屬性K
並生成僅需要該屬性的T
版本 ( Pick<T, K>
) 和 rest 是可選( Partial<T>
),並產生這些的聯合。
例如,如果我們對A
的properties
屬性執行此操作,我們會得到:
type AtLeastOnePropFromAProperties = AtLeastOneProp<A["properties"]>;
/* type AtLeastOnePropFromAProperties = {
p1: string;
p2?: string | undefined;
} | {
p2: string;
p1?: string | undefined;
} */
您可以看到要么需要p1
而不需要p2
,要么需要p2
而不需要p1
。
“ TypeScript a | b 允許兩者的組合” 創建一個類型 function ExclusifyUnion<T>
T
object的聯合類型的聯合轉換為其他成員類型的聯合:其中每個聯合成員拒絕屬於此類聯合的其他成員的屬性
type AllKeys<T> = T extends any ? keyof T : never;
type ExclusifyUnion<T, K extends AllKeys<T> = AllKeys<T>> =
T extends any ? (T & Partial<Record<Exclude<K, keyof T>, never>>) extends
infer O ? { [P in keyof O]: O[P] } : never : never;
這也是使用分布式條件類型和實用程序類型。 AllKeys<T>
是T
的所有聯合成員的所有鍵的聯合,並且ExclusifyUnion<T>
獲取T
的每個聯合成員並添加Partial<Record<Exclude<K, keyof T>, never>>
,其中K
是來自完整聯合的AllKeys<T>
。 Exclude<K, keyof T>
只查看當前聯合成員中不存在的完整聯合中的那些鍵,而Partial<Record<..., never>>
生成一個 object 類型,其值必須缺失這些鍵(或undefined
)。
如果我們在A
和B
的properties
的聯合上使用它,我們得到:
type ExclusifyAorBProperties = ExclusifyUnion<A["properties"] | B["properties"]>
/* type ExclusifyAorBProperties = {
p1: string;
p2: string;
p3?: undefined;
p4?: undefined;
} | {
p3: string;
p4: string;
p1?: undefined;
p2?: undefined;
} */
您可以看到, p1
和p2
是必需的,而p3
和p4
是被禁止的,或者是p3
和p4
是必需的,而p1
和p2
是被禁止的。
對於您想要的類型,我們會將AtLeastOneProp<T>
和ExclusifyUnion<T>
與您的A
和B
類型結合起來:
type MyType = {
identifier: string,
properties: ExclusifyUnion<AtLeastOneProp<A["properties"] | B["properties"]>>
}
如果您使用 IntelliSense 檢查properties
,您將看到這是如何工作的,至少在類型被截斷之前:
/* (property) properties: {
p1: string;
p2?: string | undefined;
p3?: undefined;
p4?: undefined;
} | {
p2: string;
p1?: string | undefined;
p3?: undefined;
p4?: undefined;
} | {
p3: string;
p4?: string | undefined;
p1?: undefined;
p2?: undefined;
} | {
...;
} */
讓我們在您的用例上進行測試:
const a: MyType = { identifier: 'id', properties: { p1: 'prop1', p2: 'prop2' } }; // ok
const b: MyType = { identifier: 'id', properties: { p2: 'prop2' } }; // ok
const c: MyType = { identifier: 'id', properties: { p3: 'prop3', p4: 'prop4' } }; // ok
const d: MyType = { identifier: 'id', properties: { p3: 'prop3' } }; // ok
const e: MyType = { identifier: 'id', properties: {} } // error!
// Type '{}' is not assignable -----> ~~~~~~~~~~
const f: MyType = { identifier: 'id' } // error!
// ~ <-- Property 'properties' is missing
const g: MyType = { identifier: 'id', properties: { p1: 'prop1', p3: 'prop3' } } // error!
// ---------------------------------> ~~~~~~~~~~
// Type '{ p1: string; p3: string; } is not assignable
看起來不錯!
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.