如何将接口属性类型限制为已声明枚举的子集?

时间:2019-07-04 21:30:09

标签: typescript enums

我有一个外部模块声明了一堆枚举:

declare enum A {
B,
C,
D
}

然后我根据这些枚举创建接口:

interface ISomeProps {
someProp: A
}

但是,在某些情况下,我想将someProp的类型限制为A的一部分,例如:

interface ISomeRestrictedProps {
restrictedProp: A.B | A.C
}

是否可以在不声明个案子集的情况下声明新的枚举(这是我目前的工作方式)?

我尝试使用Extract或并集类型,但是由于出现TS错误,所以无法引用已声明枚举的枚举实例:

enum has members with initializers that are not literals.

2 个答案:

答案 0 :(得分:1)

您使用工会的方法是正确的。问题在于枚举声明。 Typescript不允许您使用枚举成员的并集,除非它知道这些枚举的值。不确定为什么?会追踪到PR,它明确指出了该规则:

  

当枚举类型的每个成员都具有一个自动分配的值,一个指定数字文字的初始化程序或一个指定另一个枚举成员的单个标识符的初始化程序时,该枚举类型被视为联合枚举类型。联合枚举类型的成员既可以用作常量也可以用作类型,并且枚举类型等效于声明的成员类型的联合。

自声明枚举以来,它不会自动分配值,并且由于没有显式值,因此也不会被视为枚举成员的并集。

这将起作用,但是我不确定是否可以更改枚举定义:

declare enum A {
    B = 0,
    C = 1,
    D = 2
}

interface ISomeRestrictedProps {
    restrictedProp: A.B | A.C
}

答案 1 :(得分:0)

唯一的解决方法是以不同的方式声明枚举。

执行此操作的一种方法非常冗长,但其行为很像声明的具有未知值的枚举。 TypeScript中的true枚举用少量代码添加了很多命名类型和值,因此要手动模拟它,我们需要很多行。通用技术是为AA.BA.CA.D中的每一个声明一个值和一个类型,并以如下方式处理每个枚举值的类型互不相同,却不知道它们的实际含义:

declare namespace A {
  interface _B { readonly __nominal: unique symbol; }
  export type B = _B & number;
  export const B: B;

  interface _C { readonly __nominal: unique symbol; }
  export type C = _C & number;
  export const C: C;

  interface _D { readonly __nominal: unique symbol; }
  export type D = _D & number;
  export const D: D;
}
type A = A.B | A.C | A.D;

在上面,{ readonly __nominal: unique symbol }用作branding技术,使用unique symbols使TypeScript对待A.BA.C和{{1 }}标为名义进行打字,因此尽管具有“相同”结构,但彼此之间的类型却截然不同。这一点对编译器来说是骗人的,因为显然在运行时A.D不会有一个名为A.C的属性,但是只要您忽略它,它就可以正常工作。

请确保:如果您具有完整枚举类型的变量,则可以为其分配任何成员:

__nominal

但是,如果您的变量只是成员类型之一,则不能分配其他成员类型:

let a: A = A.B; // okay
a = A.C; // okay
a = A.D; // okay

现在允许您输入类型:

let b: A.B = A.B; // specifically only A.B
b = A.C; // error! A.C not assignable to A.B
b = A.D; // error! A.D not assignable to A.B

并按照您的意愿或多或少地表现:

interface ISomeRestrictedProps {
  restrictedProp: A.B | A.C;
}

Link to branded-enumlike code


类似地,您可以执行@ TitianCernicova-Dragomir suggested并将值赋给枚举。只要它们彼此不同,它们就不必一定是 actual 值,并且您不会犯把它们视为真实值的错误:

const i: ISomeRestrictedProps = {
  restrictedProp: A.B // okay
};
i.restrictedProp = A.C; // okay
i.restrictedProp = A.D; // error! D not assignable to B | C

同样,您对编译器撒谎是确切的值,但是只要您只是忽略了确切的值并且不编写关心确切值的代码,就可以了。后续代码应该都可以按您期望的方式工作(并且我不会重复上面的代码...您可以在下面的链接中查看其工作方式)

Link to dummy-val-enum code


在这两种方法中,您都需要向编译器撒谎...前一种方法很冗长,但并不假装您知道枚举的数值,而后一种方法更简单,但可能会运行更多粗心的开发人员认为虚拟值是实际值的风险。

无论如何,希望能有所帮助;祝你好运!