[英]How to make one object type which will contain properties of all objects of union type, but values will are intersections
如何創建一種類型,該類型將從對象的聯合中創建一個對象類型,該類型將包含聯合類型的所有對象的屬性,但值將相交?
示例:我需要從 type { foo: 1 } | { foo: 2; bar: 3 } | { foo: 7; bar: 8 }
{ foo: 1 } | { foo: 2; bar: 3 } | { foo: 7; bar: 8 }
{ foo: 1 } | { foo: 2; bar: 3 } | { foo: 7; bar: 8 }
一個類型{foo: 1 | 2 | 7; bar: 3 | 8}
{foo: 1 | 2 | 7; bar: 3 | 8}
{foo: 1 | 2 | 7; bar: 3 | 8}
。
重要說明:我想創建一種對象類型,而不是像{foo: 1 | 2} & {bar: 3}
{foo: 1 | 2} & {bar: 3}
我寫了一個類型ComplexUnionToIntersection
,它應該這樣做,但它忽略了聯合的所有對象中不存在的屬性(我的示例中的bar
)。
我的代碼:
/**
* More info: https://fettblog.eu/typescript-union-to-intersection/
*/
export type UnionToIntersection<U> = (
U extends any ? (k: U) => void : never
) extends (k: infer I) => void
? I
: never;
/**
* Target type
*/
export type ComplexUnionToIntersection<U> = { o: U } extends { o: infer X }
? {
[K in keyof (X & U)]: (X & U)[K];
}
: UnionToIntersection<U>;
測試用例:
// TODO: result of test case must be `{foo: 1 | 2; bar: 3}`
type testCase1 = ComplexUnionToIntersection<{ foo: 1 } | { foo: 2; bar: 3 }>; // actually return `{ foo: 1 | 2; }`
// TODO: result of test case must be `{foo: 1 | 2 | 7; bar: 3 | 8}`
type testCase2 = ComplexUnionToIntersection<
{ foo: 1 } | { foo: 2; bar: 3 } | { foo: 7; bar: 8 }
>;
// TODO: result of test case must be `{foo: 1 | 2; bar: 3 | 8}`
type testCase3 = ComplexUnionToIntersection<
{ foo: 1 } | { foo: 2; bar: 3 } | { bar: 8 }
>;
// TODO: result of test case must be `{foo?: 1 | 2; bar: 3 | 8}`
type testCase4 = ComplexUnionToIntersection<
{ foo: 1 } | { foo?: 2; bar: 3 } | { bar: 8 }
>;
所以操作是將對象類型的並集合並為單個對象類型,其中任何輸入並集成員中的每個屬性鍵都會出現在輸出類型中,並且該屬性的值類型將是所有屬性值的並集輸入中出現該鍵的任何位置的類型。 如果某個屬性在任何輸入聯合成員中是可選的,那么您希望該屬性在輸出中是可選的。
如果您只是將聯合折疊為單個對象類型,您將遇到問題,您會看到一個屬性僅在每個輸入中都存在時才會存在於輸出中,否則屬性類型將是正確的。 所以這里的一種方法是首先增加聯合的每個成員,使其包含來自任何聯合成員的所有密鑰。 因此,如果缺少某個屬性,我們應該添加它,並為其賦予never
類型的值類型,該類型被任何聯合所吸收。
例如,我們從以下內容開始:
{ foo: 1 } | { foo?: 2; bar: 3 } | { bar: 8 }
然后擴充聯合的每個成員以包含所有鍵,例如:
{ foo: 1; bar: never } | { foo?: 2; bar: 3 } | { foo: never; bar: 8 }
然后我們將這個新聯合合並到一個對象中,例如:
{ foo?: 1 | 2 | never; bar: never | 3 | 8 }
崩潰到
{ foo?: 1 | 2; bar: 3 | 8 }
所以讓我們這樣做:
type AllKeys<T> = T extends unknown ? keyof T : never
type AddMissingProps<T, K extends PropertyKey = AllKeys<T>> =
T extends unknown ? (T & Record<Exclude<K, keyof T>, never>) : never;
type MyMerge<T> = { [K in keyof AddMissingProps<T>]: AddMissingProps<T>[K] }
AllKeys<T>
類型是一種分布式條件類型,它從每個聯合成員那里收集每個鍵:
type TestAllKeys = AllKeys<{ foo: 1 } | { foo?: 2; bar: 3 } | { bar: 8 }>
// type TestAllKeys = "foo" | "bar"
AddMissingProps<T, K>
類型也是可分配的,對於輸入聯合T
的每個元素,它會添加K
中尚未出現在keyof T
中的任何鍵,並給它們一個never
類型,並注意K
默認為AllKeys<T>
:
type TestAddMissingProps = AddMissingProps<{ foo: 1 } | { foo?: 2; bar: 3 } | { bar: 8 }>
/* type TestAddMissingProps =
({ foo: 1; } & Record<"bar", never>) |
({ foo?: 2 | undefined; bar: 3; } & Record<never, never>) |
({ bar: 8; } & Record<"foo", never>) */
這相當於{ foo: 1; bar: never } | { foo?: 2; bar: 3 } | { foo: never; bar: 8 }
{ foo: 1; bar: never } | { foo?: 2; bar: 3 } | { foo: never; bar: 8 }
{ foo: 1; bar: never } | { foo?: 2; bar: 3 } | { foo: never; bar: 8 }
上面提到的類型,雖然寫法不一樣。 由於我們仍然要處理類型,所以在這里減少它並不重要。
最后, MyMerge<T>
類型是AddMissingProps<T>
上的標識映射類型。 它的唯一目的是遍歷每個屬性並生成單個對象類型輸出:
type TestMyMerge = MyMerge<{ foo: 1 } | { foo?: 2; bar: 3 } | { bar: 8 }>
/* type TestMyMerge = {
foo?: 1 | 2 | undefined;
bar: 3 | 8;
} */
看起來不錯!
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.