簡體   English   中英

Typescript:將類型定義為具有部分存在的 object 的類型的聯合

[英]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> ),並產生這些的聯合。

例如,如果我們對Aproperties屬性執行此操作,我們會得到:

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 )。

如果我們在ABproperties的聯合上使用它,我們得到:

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;
} */

您可以看到, p1p2是必需的,而p3p4是被禁止的,或者是p3p4是必需的,而p1p2是被禁止的。


對於您想要的類型,我們會將AtLeastOneProp<T>ExclusifyUnion<T>與您的AB類型結合起來:

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

看起來不錯!

Playground 代碼鏈接

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM