【问题标题】:How to restrict interface property type to subset of a declared enum?如何将接口属性类型限制为声明的枚举的子集?
【发布时间】:2019-07-04 21:30:09
【问题描述】:

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

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.

【问题讨论】:

    标签: typescript enums


    【解决方案1】:

    您使用联合的方法是正确的。问题在于枚举声明。 Typescript 不会让您使用枚举成员的联合,除非它知道这些枚举的值。不知道为什么 ? 将其追踪到此 PR,其中明确说明了此规则:

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

    由于声明了枚举,它不会自动分配值,并且由于没有明确的值,它也不会被视为枚举成员的联合。

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

    declare enum A {
        B = 0,
        C = 1,
        D = 2
    }
    
    interface ISomeRestrictedProps {
        restrictedProp: A.B | A.C
    }
    

    【讨论】:

    • 感谢您的回复!不幸的是,更改声明不是一种选择,因为我们实际上是从服务器下载包含枚举实际值的文件。这似乎很不幸......我会暂时保留这个问题,看看是否有人有任何其他想法,如果没有,我会接受这个答案。再次感谢!
    【解决方案2】:

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

    执行此操作的一种方法非常冗长,但其行为很像声明的具有未知值的枚举。 TypeScript 中的真正枚举使用少量代码添加了许多命名类型和值,因此要手动模拟它,我们需要很多行。通用技术是为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.CA.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;
    }
    

    并且表现得或多或少像你想要的那样:

    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 branded-enumlike code


    同样,您可以执行 @TitianCernicova-Dragomir suggested 并为枚举赋值。它们不一定是 实际 值,只要它们彼此不同并且您不会错误地将它们视为实际值:

    declare enum A {
      B = 0xbbbbb, // dummy value for B
      C = 0xccccc, // dummy value for C
      D = 0xddddd  // dummy value for D
    }
    

    再一次,你在对编译器撒谎关于确切的值,但只要你忽略确切的值并且不编写关心确切值的代码,应该没问题。后面的代码应该都如你所愿(我不会重复上面的代码……你可以在下面的链接中看到它是如何工作的)

    Link to dummy-val-enum code


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

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

    【讨论】:

    • 哇,感谢您的彻底回复。学到了很多 :) 听起来没有一个很好的方法来做到这一点......但展望未来,我不确定我们应该首先使用枚举。
    【解决方案3】:

    对于我的用例,Exclude 实用程序类型非常适合此。

    
    export enum TransactionSet {
      "NormalTransactions" = "NormalTransactions",
      "TransactionsWithErrors" = "TransactionsWithErrors"
    }
    
    type ActionPayload = { transactionsSet: Exclude<TransactionSet, TransactionSet.TransactionsWithErrors> };
    
    const action1: ActionPayload = { transactionsSet: TransactionSet.NormalTransactions }; // OK
    const action2: ActionPayload = { transactionsSet: TransactionSet.TransactionsWithErrors }; // Not assignable 
    
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-09-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多