繁体   English   中英

如何展平 Typescript 中的嵌套联合类型?

[英]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 };

我尝试使用映射类型和各种实用程序类型,但无法弄清楚。 这是真的可以做的事情吗?

typescript游乐场链接

将并集转换为交集的实用程序类型是基础:我从这里获取

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 类型,但目的是将nameeventName放在同一级别。

中间结果是这样的:

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

算法:

  1. 将联合类型转换为数组。 参见 UnionToArray
  2. 遍历数组并更改所有元素。 请参阅映射和谓词
  3. 将数组转换为联合

操场

在这里你可以找到更多的例子

我有一个类型的联合,它的属性也包含联合类型:

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM