[英]How to flatten nested union types in Typescript?
我有一个类型的联合,其属性也包含联合类型:
type Union =
| {
name: "a";
event:
| { eventName: "a1"; payload: string }
| { eventName: "a2"; payload: number };
}
| {
name: "b";
event: { eventName: "b1"; payload: boolean };
};
我正在尝试将其展平以获得这样的单一联合类型:
type Result =
| { name: "a"; eventName: "a1"; payload: string }
| { name: "a"; eventName: "a2"; payload: number }
| { name: "b"; eventName: "b1"; payload: boolean };
我尝试使用映射类型和各种实用程序类型,但无法弄清楚。 这是真的可以做的事情吗?
将并集转换为交集的实用程序类型是基础:我从这里获取
type Union =
| {
name: "a";
event:
| { eventName: "a1"; payload: string }
| { eventName: "a2"; payload: number };
}
| {
name: "b";
event: { eventName: "b1"; payload: boolean };
};
type nested = {
[n in Union['name']]: {
[e in Extract<Union, { name: n }>['event']['eventName']]: {
name: n,
eventName: e,
payload: Extract<Union['event'], {eventName: e}>['payload']
}
}
}
// https://stackoverflow.com/a/50375286/3370341
type UnionToIntersection<U> =
(U extends any ? (k: U)=>void : never) extends ((k: infer I)=>void) ? I : never
type r = UnionToIntersection<nested[keyof nested]>
type Result = r[keyof r]
type Expected =
| { name: "a"; eventName: "a1"; payload: string }
| { name: "a"; eventName: "a2"; payload: number }
| { name: "b"; eventName: "b1"; payload: boolean };
declare const expected: Expected;
declare const result: Result;
// In case types are not the same I expect a type error here
const y: Result = expected
const l: Expected = result
我想知道是否有更简单的方法。
我首先创建了一个嵌套的 object 类型,但目的是将name
和eventName
放在同一级别。
中间结果是这样的:
type nested = {
a: {
a1: {
name: 'a',
eventName: 'a1',
payload: string
},
a2: {
name: 'a',
eventName: 'a2',
payload: number
}
},
b: {
b1: {
name: 'b',
eventName: 'b1',
payload: boolean
}
}
}
然后我提取了一个只有内部值类型的联合。
我试过在eventName
上使用Extract
之类的东西,但是当它适用于'b1'
时,它会导致never
with 'a1'
和'a2'
。
type b1 = Extract<Union, {event: {eventName: 'b1'}}> // ok
type a1 = Extract<Union, {event: {eventName: 'a1'}}> // never
比@Ilario Pierbattista 更丑陋的解决方案,但有效:
//https://stackoverflow.com/questions/50374908/transform-union-type-to-intersection-type/50375286#50375286
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (
k: infer I
) => void
? I
: never;
//https://github.com/microsoft/TypeScript/issues/13298#issuecomment-468114901
type UnionToOvlds<U> = UnionToIntersection<
U extends any ? (f: U) => void : never
>;
type PopUnion<U> = UnionToOvlds<U> extends (a: infer A) => void ? A : never;
type UnionToArray<T, A extends unknown[] = []> = IsUnion<T> extends true
? UnionToArray<Exclude<T, PopUnion<T>>, [PopUnion<T>, ...A]>
: [T, ...A];
// https://stackoverflow.com/questions/53953814/typescript-check-if-a-type-is-a-union#comment-94748994
type IsUnion<T> = [T] extends [UnionToIntersection<T>] ? false : true;
type ArrayOfUnions<T> = IsUnion<T> extends true ? UnionToArray<T> : T
//https://www.typescriptlang.org/docs/handbook/2/conditional-types.html#distributive-conditional-types
type Distributive<T> = [T] extends [any] ? T : never
// treat this predicate as Array.prototype.map predicate
type MapPredicate<T> =
T extends { event: object, name: string }
? IsUnion<T['event']> extends true
? ArrayOfUnions<T['event']> extends any[]
? Distributive<ArrayOfUnions<T['event']>[number]> & { name: T['name'] }
: T
: T['event'] & { name: T['name'] }
: never
//https://catchts.com/tuples#map
// This util works similar to Array.prototype.map
type Mapped<
Arr extends Array<unknown>,
Result extends Array<unknown> = []
> = Arr extends []
? []
: Arr extends [infer H]
? [...Result, MapPredicate<H>]
: Arr extends [infer Head, ...infer Tail]
? Mapped<[...Tail], [...Result, MapPredicate<Head>]>
: Readonly<Result>;
type Union =
| {
name: "a";
event:
| { eventName: "a1"; payload: string }
| { eventName: "a2"; payload: number };
}
| {
name: "b";
event: { eventName: "b1"; payload: boolean };
};
type Result = Mapped<UnionToArray<Union>>[number]
type Expected =
| { name: "a"; eventName: "a1"; payload: string }
| { name: "a"; eventName: "a2"; payload: number }
| { name: "b"; eventName: "b1"; payload: boolean };
type Assert = Result extends Expected ? true : false // true
算法:
在这里你可以找到更多的例子
我有一个类型的联合,它的属性也包含联合类型:
type Union =
| {
name: "a";
event:
| { eventName: "a1"; payload: string }
| { eventName: "a2"; payload: number };
}
| {
name: "b";
event: { eventName: "b1"; payload: boolean };
};
我正在尝试将其展平以获得这样的单一联合类型:
type Result =
| { name: "a"; eventName: "a1"; payload: string }
| { name: "a"; eventName: "a2"; payload: number }
| { name: "b"; eventName: "b1"; payload: boolean };
我尝试使用映射类型和各种实用程序类型,但无法弄清楚。 是不是真的可以做到?
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.