【问题标题】:Why does a string literal not match enum为什么字符串文字与枚举不匹配
【发布时间】:2019-11-25 16:29:03
【问题描述】:

我正在尝试在打字稿中使用枚举,但它们的类型检查似乎并不十分一致。为什么我可以在没有警告的情况下检查TestEnum.Foo === 'foo',但尝试将'foo' 传递给接受TestEnum 的函数会导致错误。

describe('Test enum functionality', () => {
  enum TestEnum {
    Foo = 'foo',
    Bar = 'bar'
  }

  // I expected this to work as TestEnum.Foo === 'foo'
  test('Can pass string to enum', () => {
    const func = (x: TestEnum) => {}
    // Error: Argument of type '"foo"' is not assignable to parameter of type 'TestEnum'
    func('foo');
  });

  // Surprised that this worked after I couldn't pass in a string literal to a function
  test('compiler can verify that this string literal is an Enum option', () => {
    if (TestEnum.Foo === 'foo') {

    }
  });

  // I expect an error here because the compiler should be able to see there is no overlap
  test('compiler can verify that this string literal is not an Enum option', () => {
    // Error: This condition will always return 'false' since the types 'TestEnum.Foo' and '"asdf"' have no overlap
    if (TestEnum.Foo === 'asdf') {

    }
  });
});

更新

我删除了一些单元测试以更清楚地说明我为什么感到困惑

【问题讨论】:

    标签: typescript enums


    【解决方案1】:

    字符串值enum 被认为是相关字符串文字的子类型。特别是,TestEnum.Foo 的类型可分配给字符串文字 "foo",但反之则不行。这是enum 值的一部分;它们旨在按名称使用,而不是按价值使用。它是一个抽象层,允许您更改枚举的值,而无需重构其余代码。 (出于历史原因,此限制为 not implemented for numeric enums,可用作位标志;对于数字枚举,您几乎可以为枚举类型分配任何数字或从枚举类型分配任何数字。)

    TestEnum"foo" | "bar" 之间的不对称可分配性是 func("foo") 失败的原因。以下分配是安全的:

    const okay: TestEnum = TestEnum.Foo; // okay
    const alsoOkay: "foo" | "bar" = TestEnum.Foo; // okay, widening to supertype
    

    但这是一个错误,因为它缩小到一个子类型,编译器认为这是不安全的:

    const bad: TestEnum = "foo"; // error
    

    这类似于以下被视为不安全的方式:

    const oops: string = Math.random() < 100 ? "hey" : 1; // error
    

    即使在运行时,oops 肯定会是一个“字符串”,但编译器只能验证它是 string | number 类型(嗯,"hey" | 1)所以它不能允许它。

    如果你想这样做,你需要type assertion

    const assertOkay: TestEnum = "foo" as TestEnum; // okay
    

    (意思是func("foo" as TestEnum) 会为你工作)


    至于与=== 的比较,TypeScript 检查(参见absolutely humongous checker.ts file 中的isTypeEqualityComparableTo() 函数)每个操作数的类型是“相关的”。通常,如果其中一个类型是另一个类型的子类型,则两种类型是相关的,如果一个值不可能同时属于这两种类型,则它们是不相关的。 (实际的检查要复杂得多,但这些提供了一个很好的经验法则。)这会导致以下行为:

    ("foo" === TestEnum.Foo); // okay, TestEnum.Foo is a subtype of "foo"
    ("bar" === TestEnum.Foo); // error, no overlap between "bar" and TestEnum.Foo
    const someTestEnum = Math.random() < 0.5 ? TestEnum.Foo : TestEnum.Bar;
    ("bar" === someTestEnum); // okay, overlapping types
    

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

    Link to code

    【讨论】:

      【解决方案2】:

      Typescript 正在做你想做的事。它告诉你 'asdf' !== 'foo' 并且永远不会这样,所以表达式总是计算为假。这是因为'asdf''foo' 都被视为string literal 类型。请注意,以下内容不会给您错误:

      if('foo' === Foo.foo) {
      
      }
      
      if(('asdf' as string) !== Foo.foo) {}
      

      在第一种情况下,打字稿理解'foo' === 'foo',因此不会引发错误。在第二种情况下,我们将 'asdf'string literal 类型转换为通用 string 类型,并且比较不会出错,因为 typescript 已被告知 'asdf' 实际上可能是任何字符串,因此可能是等于'foo'

      至于为什么你不能将字符串文字传递给函数,我建议你参考这个question,但本质上答案是,打字稿希望你传递对枚举的引用,而不是文字本身。枚举的意义在于不必知道它们的字面值是什么。

      【讨论】:

      • 我更新了我的问题,以便更清楚地关注问题。我只是想了解为什么打字稿需要对枚举的引用,因为它应该能够看到有重叠......有时它可以。
      • 区别在于使用枚举作为值,而不是使用它作为类型。在 if 语句中,您使用 TestEnum.Foo 作为计算结果为 'foo' 的值,因此它是字符串文字 'foo',但在函数中,您指定 x 的类型为 TestEnum,它告诉 typescript 只允许要通过引用传递的值。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-09-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多