【问题标题】:TS compiler not happy about union type involving enum brand (nominal typing)TS 编译器对涉及枚举品牌的联合类型不满意(名义类型)
【发布时间】:2019-08-27 02:56:08
【问题描述】:

我正在使用枚举来实现名义输入(例如在 TypeScript Deep Dive 书中建议的):

enum ExampleIdBrand {}
export type ExampleId = ExampleIdBrand & string
const exampleId: ExampleId = '42' as ExampleId

const m1 = (e: ExampleId) => e.toUpperCase()
m1(exampleId) // ✅

到目前为止,一切都按预期进行。但是,如果我将方法更改为接受(更广泛的)联合类型,编译器将不再接受我的exampleId

const m2 = (e: ExampleId | 'whatever') => e.toUpperCase()
m2('whatever') // ✅
m2(exampleId) // ???? Does not compile

为什么最后一行编译得不好? (TS 3.3.4000)

【问题讨论】:

  • 嗨,Rahel,您使用特殊方言吗? .toUpperCase() 是一个函数,它需要大括号。
  • 没有特殊的方言,只是我马虎——会解决的,tx!
  • @DanielDietrich 它也可以这样工作.. 它只是做了一些不同的事情..
  • 是的,我已经摆弄了一下。 const exampleId: ExampleId = '42' as ExampleId; 的演员阵容已经是一种气味。如果我们删除演员表并使 ExampleId 成为字符串export type ExampleId = string;,它就可以工作。似乎无法使用枚举创建 ~~union~~ 交集类型。会google一下...
  • 好吧,但是我们失去了名义上的打字(netzwerg.ch/blog/2018/11/21/react-redux-typescript/#5

标签: typescript types union-types


【解决方案1】:

在 TypeScript 中,枚举可以是数字或字符串类型。数字没有 .toUpperCase() 方法。

您的示例应该可以工作,因为枚举被缩小为字符串类型。

解决方法是:

enum ExampleIdBrand {}
export type ExampleId = ExampleIdBrand;
const exampleId: ExampleId = '42';

const m1 = (e: ExampleId) => e.toString().toUpperCase();
m1(exampleId);

const m2 = (e: ExampleId | 'whatever') => e.toString().toUpperCase();
m2('whatever'); // ✅
m2(exampleId); // ✅

【讨论】:

  • 不过,你是对的,Rahel。在“TypeScript Deep Dive”之后,我还希望 ExampleId & string 将枚举缩小为字符串类型。您的示例应该编译,但它没有。也许是编译器错误?在我上面的示例中,根本不需要联合。我将编辑帖子...
  • 这不是用户想要做的,他们想要创建一个类型,虽然运行时的字符串与编译器时的任何其他字符串不兼容,但至少没有类型断言。 stackoverflow.com/questions/49557714/…
  • 这听起来确实是一个很好的解决方法,谢谢@DanielDietrich。但是和你一样,我还是不明白为什么我的初始示例无法编译?
【解决方案2】:

作为空集的交集(即没有可以作为交集实例的值)所发生的事情已经发生了变化。我真的找不到文档,虽然我会继续寻找,但在某些情况下,这样的交叉点会崩溃到永远。我们在这种情况下看到了这一点,ExampleId | 'whatever' = never | 'whatever' = 'whatever'

const m2 = (e: ExampleId | 'whatever') => e.toUpperCase()
type x =  Parameters<typeof m2>[0] //  'whatever'

为了保持ExampleId 的名义性质,我们可以添加一个属性:

enum ExampleIdBrand {}
export type ExampleId = { __brand: ExampleIdBrand } & string
const exampleId: ExampleId = '42' as ExampleId

const m1 = (e: ExampleId | "whatever") => e.toUpperCase()
m1(exampleId) // ✅
m1("whatever")// ✅

或者,如果我们想很好地隐藏该成员,我们可以使用与具有私有字段的类的交集:

enum ExampleIdBrand { }
class Brand<T> { private __brand: T}
export type ExampleId = Brand<ExampleIdBrand> & string
const exampleId: ExampleId = '42' as ExampleId

const m1 = (e: ExampleId | "whatever") => e.toUpperCase()
m1(exampleId) // ✅
m1("whatever")// ✅

或者删除枚举并使用这个class ExampleIdBrand { private __brand!: any}

【讨论】:

  • 非常感谢您的建议。 “一个不能真正创建的交叉点会发生什么”——您介意详细说明为什么这个交叉点“不能创建”吗?
  • @RahelLüthy 使用固定语言澄清了一点。我认为这些是相关的 PR:github.com/Microsoft/TypeScript/issues/20001, github.com/Microsoft/TypeScript/pull/23751
  • 哇,PR 23751 似乎解释了它:“10 | 数量减少到只有 10” - 这不是我期望它的工作方式? 你知道这一点吗,@DanielDietrich?
  • @RahelLüthy 这是一个错字,10 | numbernumber10 &amp; number10
  • 那肯定更有意义!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-02-14
  • 1970-01-01
  • 1970-01-01
  • 2018-07-04
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多