【问题标题】:Extract a union type in tagged union在标记的联合中提取联合类型
【发布时间】:2020-09-22 19:47:12
【问题描述】:

我的代码中有一个类型

   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 和 ...),我必须重复所有这些键。

有没有办法在不提供t2,t3,...的情况下从Test中提取t1

Playground

更新

Test 类型是为了提问而简化的,我正在寻找 dynamic 方法来实现这一点,并且与Test 中的其他字段无关(因为类型经常更改)

【问题讨论】:

    标签: typescript


    【解决方案1】:

    您可以编写自定义提取函数并使用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 };
    

    【讨论】:

    • 我想创建多个类型,它不仅适用于t1,每个类型我都有一个函数,我想在 args 中排除它们,我正在寻找答案以避免写 @987654324 @everytime,你的回答是只针对“t1”,有什么办法改进吗?
    • 另外为了简化我的问题,我只添加了data,但还有更多的字段,正如我每次更改Test 类型时看到的那样,我需要更改TestExtract 类型,我'我只是在寻找更多dynamic方式
    • 您可以通过添加第二个泛型来改进这一点:type TestExtract&lt;T, S&gt; = T extends { selector: "t1" | "t2" | "t3", data: infer U } ? { selector: S, data: U } : never; 然后您可以选择您想要的选择器TestExtract&lt;Test, 't1'&gt;
    【解决方案2】:

    如果您使用的是 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 拆分为它的联合成员,并且对于每个成员,看看它在与U 相交时是否减少为never。如果是这样,那么我们不想要那个工会成员。如果没有,那么我们确实想要它。这会产生你想要的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&lt;T, U&gt;。如果你可以分别指定判别属性名称和值,你可以这样做:

    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

    【讨论】:

    • 我接受这个是因为,这是最好的动态方式。谢谢
    【解决方案3】:

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

    如果Test union 将只包含原始帖子中描述的成员,最简单的解决方案是解决反向问题。不是提取联合体的第一个成员,而是排除联合体的第二个成员。

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

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

    这个解决方案更复杂,但也适用于更大的工会。

    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

    【讨论】:

    • 这仍然会给他与type Data = Extract&lt;Test , { selector: "t1" | "t2" | "t3" }&gt;相同的结果
    • 另一个选择器不是一个。不止这两个
    • 您的答案可能比我当时建议的要好。干得好???
    • 我更新了我的答案。它应该适用于任何规模的工会。
    猜你喜欢
    • 2023-03-17
    • 2019-03-27
    • 1970-01-01
    • 2020-10-16
    • 1970-01-01
    • 2017-03-04
    • 2018-09-28
    相关资源
    最近更新 更多