在标记的联合中提取联合类型

时间:2020-06-03 23:11:39

标签: typescript

我的代码中有一个类型

   type Test = {
      selector:
        | "t1"
        | "t2"
        | "t3"; // and t4 t5 ...
      data: any;
    }
  | {
      selector: "temp";
      data: any;
      prev: any;
    };

所以我想知道如何从这种类型中提取t1

我已经看到以下答案:https://stackoverflow.com/a/52943170/1827594

这很好

type Data = Extract<Test , { selector: "t1" | "t2" | "t3" /* and t4 t5 */ }>

但这不起作用

type Data = Extract<Test , { selector: "t1" }> // the type of Data is never

因此,如果它是两个以上的并集类型(t3,t4和...),则必须重复所有这些键。

有什么方法可以在不提供t1的情况下从Test提取t2,t3,...吗?

Playground

更新

Test类型简化了问的过程,我正在寻找实现这一目标的 dynamic 方法,并且与Test中的其他字段无关(因为类型更改频繁)

3 个答案:

答案 0 :(得分:2)

您可以编写自定义提取函数并使用the infer keyword

 type Test = {
      selector:
        | "t1"
        | "t2"
        | "t3"; // and t4 t5 ...
      data: number;
    }
  | {
      selector: "temp";
      data: any;
      prev: any;
 };

type TestExtract<T> = T extends { selector: "t1" | "t2" | "t3", data: infer U }
  ? { selector: "t1", data: U }
  : never; 

type Data = TestExtract<Test> // {selector: "t1" , data: number };

答案 1 :(得分:1)

如果Test联合只包含这两个定义明确的成员

如果Test联合仅包含原始帖子中描述的成员,则最简单的解决方案是解决相反的问题。 提取工会的第一位成员,而不是排除工会的第二位成员。

type Data = Exclude<Test, { selector: "temp" }>

如果Test联合包含任意数量的成员

此解决方案较为复杂,但也适用于较大的工会。

interface Selectable {
  selector: string;
}

type Extractor<T extends Selectable, U extends Selectable> =
  // For each member of the union T...
  T extends any
    // See if the member overlaps with U.
    ? U extends Pick<T, 'selector'>
      // If it does, return that member of the union.
      ? T
      // If not, return never.
      : never
    : never;

type Data = Extractor<Test, { selector: 't1' }>;

Playground


请注意T extends any的使用。看起来很尴尬-毕竟,每种类型都扩展any,因此始终满足此条件。但是,使用条件类型使您可以迭代我们想要的联合的每个成员。进一步了解distributive conditional types

答案 2 :(得分:1)

如果您使用的是TS3.9 +(aggressively reduces intersections of types with conflicting discriminant properties to never),则可以这样写:

// TS 3.9+
type ExtractOverlaps<T, U> = T extends any ? (U & T extends never ? never : T) : never;

type Data = ExtractOverlaps<Test, { selector: "t1" }>;
/* type Data = {
    selector: "t1" | "t2" | "t3";
    data: any;
} */

在这里,我们将T分成其联合成员,并针对每个成员,查看与never相交时它是否减少为U。如果是这样,那么我们就不需要那个工会成员。如果没有,那么我们确实想要它。这将产生您想要的Data


如果您仍在使用TS3.8及以下版本,则可以综合将不兼容对象的交集减少为never的操作,但这很笨拙,我不想深入探讨情况:

// works in TS3.8-
type ReduceNeverPropsToNever<T> =
  { [K in keyof T]-?: [T[K]] extends [never] ? unknown : never }[keyof T] extends
  never ? T : never;

type ExtractOverlaps<T, U> = T extends any ? (
  ReduceNeverPropsToNever<U & T> extends never ? never : T
) : never;

type Data = ExtractOverlaps<Test, { selector: "t1" }>;
/* type Data = {
    selector: "t1" | "t2" | "t3";
    data: any;
} */

当然,没有什么可以说需要用其他形状相同的东西代替Extract<T, U>。如果可以分别指定可区别的属性名称和值,则可以通过以下方式做到这一点:

type ExtractCompatible<T, K extends keyof T, V extends T[K]> =
  T extends any ? [V] extends [T[K]] ? T : never : never;

type Data = ExtractCompatible<Test, "selector", "t1">;  
/* type Data = {
  selector: "t1" | "t2" | "t3";
  data: any;
} */

在这里,我们将T拆分为其联合成员,并针对每个成员,检查V是否可分配给T[K]。我们正在明确命名属性K和值V来查找,因此我们不必进行交叉或其他奇怪的操作。除非您需要直接替换Extract,否则这可能是我建议的解决方案。


好的,希望能有所帮助;祝你好运!

Playground link to code