【问题标题】:Get Type of Typescript Union Field获取 TypeScript 联合字段的类型
【发布时间】:2021-10-12 22:14:56
【问题描述】:

我有一个看起来像这样的联合类型:

type Option1 = {
    items: string[];
}
type Option2 = {
    delete: true;
}
type Combined = Option1 | Option2;

我想用items字段的类型定义一个新变量:

const items_variable:Combined["items"] = ["a", "b"];

但这给了我错误error TS2339: Property 'items' does not exist on type 'Combined'

如果我使用的是值而不是类型(我所看到的所有现有答案都假定),我可以在这里使用类型缩小,但是我不明白如何在现有类型上做到这一点。

如果Option2 定义items,它似乎也可以按预期工作,但这对我的用例没有意义。

items_variable 应该使用什么类型?

【问题讨论】:

  • 我们不能这样做const items_variable: Option1["items"] = ["a", "b"];吗?

标签: typescript


【解决方案1】:

Playground

type Option1 = {
  items: string[];
}
type Option2 = {
  delete: true;
}
type Combined = Option1 | Option2

type UnionKeys<T> = T extends T ? keyof T : never;

type Prop<T, K extends UnionKeys<T>> = K extends UnionKeys<T> ? T[K] : never

const items_variable: Prop<Combined, 'items'> = ["a", "b"];

【讨论】:

    【解决方案2】:

    也许您正在寻找的是 Intersection 字段而不是 Union 字段:

        type Combined = Option1 & Option2;
    

    【讨论】:

      【解决方案3】:

      这就是 TS 工会的工作方式。这是设计使然。

      type Option1 = {
          items: string[];
      }
      type Option2 = {
          delete: true;
      }
      type Combined = Option1 | Option2;
      
      type Keys = keyof Combined; // never
      

      如您所见,keyof Combined 返回 never - 空键集。

      因为Option1Option2没有任何共同的props,TS不确定允许什么属性。

      假设您有一个需要Option1Option2 的函数。为了安全地使用Combined,您应该使用自定义typeguards

      type Option1 = {
          items: string[];
      }
      type Option2 = {
          delete: true;
      }
      type Combined = Option1 | Option2;
      
      type Keys = keyof Combined; // never
      
      const hasProperty = <Obj, Prop extends string>(obj: Obj, prop: Prop)
          : obj is Obj & Record<Prop, unknown> =>
          Object.prototype.hasOwnProperty.call(obj, prop);
      
      const handle = (union: Combined) => {
          if (hasProperty(union, 'items')) {
              const option = union; // Option1
          } else {
              const option = union; // Option2
          }
      
      }
      

      或者你可以添加公共属性:

      type Option1 = {
          tag: '1',
          items: string[];
      }
      type Option2 = {
          tag: '2',
          delete: true;
      }
      type Combined = Option1 | Option2;
      
      type CommonProperty = Combined['tag'] // "1" | "2"
      
      const handle = (union: Combined) => {
         if(union.tag==='1'){
             const option = union // Option1
         }
      }
      

      另一种替代方法是使用StrictUnion

      type Option1 = {
          items: string[];
      }
      type Option2 = {
          delete: true;
      }
      type Combined = Option1 | Option2;
      
      // credits goes https://stackoverflow.com/questions/65805600/type-union-not-checking-for-excess-properties#answer-65805753
      type UnionKeys<T> = T extends T ? keyof T : never;
      
      type StrictUnionHelper<T, TAll> =
          T extends any
          ? T & Partial<Record<Exclude<UnionKeys<TAll>, keyof T>, never>> : never;
      
      type StrictUnion<T> = StrictUnionHelper<T, T>
      
      type Union = StrictUnion<Combined>
      
      const items_variable: Union['items'] = ["a", "b"]; // string[] | undefined
      

      您可能已经注意到,有一个小缺点,items_variable 也可能是undefined。这是因为工会的性质。它可以是一个值,也可以是另一个值。

      【讨论】:

        【解决方案4】:

        问题在于items 只是您的工会成员中一个 的有效属性。但是您对Combined 所做的任何事情都必须对所有 工会成员有效。所以Combined['items'] 无效。


        很难从您设计的代码中提供更多建议,但不使用联合的简单答案:

        const items_variable: Option1["items"] = ["a", "b"];
        

        或者您可以在联合的所有成员上定义items 属性。请注意,它们不需要具有相同的类型。

        type Option1 = {
            items: string[];
        }
        type Option2 = {
            delete: true;
            items: never;
        }
        type Combined = Option1 | Option2;
        
        const items_variable:Combined["items"] = ["a", "b"]; // works
        

        Playground


        或者您可以使用交集将联合过滤为具有所需属性的类型。

        const items_variable: (Combined & { items: unknown })["items"] = ["a", "b"];
        

        或者

        const items_variable: (Combined & Option1)["items"] = ["a", "b"];
        

        Playground


        为了访问某个类型的['items'],该类型的所有成员必须将其作为可访问属性。所有这些方法的共同点是它们确保所有工会成员都具有此属性。

        【讨论】:

        • 我认为items: never; 对我的情况来说是一个很好的解决方案——它允许访问类型,同时也清楚地说明了代码的意图。
        猜你喜欢
        • 1970-01-01
        • 2021-10-27
        • 1970-01-01
        • 2022-01-19
        • 2020-02-25
        • 1970-01-01
        • 2019-08-04
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多