这不是一个错误;它是为了允许一个名为品牌或标记原语的功能(这一功能并没有被广泛宣传)。
在运行时,几乎不可能有一个值
v
同时是一个
string
(原始类型,其中
typeof v === "string"
,而不是一个
String
包装对象)和一个
Date
。从某种意义上说,类型
string & Date
实际上与
never
相同,因为您可以将TypeScript类型视为所有适当的JavaScript值的集合。如果没有
string & Date
值,并且没有
never
值,则这些类型在逻辑上是等效的。
因此,如果编译器急切地将交集(如
string & Date
)缩减为
never
,那么这是合理的。它已经对不兼容的单元类型的交集(例如
"a" & 0
)和不兼容的原始类型的交集(例如
string & number
)进行了这样的操作,如
microsoft/TypeScript#31838中实现的那样。那么为什么我们与原始类型交叉的对象类型不会发生这种情况呢?
目的是为了允许一种称为“品牌原语”的特性,在TypeScript中模拟
名义类型的基元(在
此FAQ条目中提到)。
TypeScript主要具有
结构类型和
类型别名;如果两个类型具有相同的结构但名称不同,则
它们是相同的类型。如果通过类型别名给现有类型命名,则
它们是相同的类型。通常这正是你想要的,但有时你希望创建两种在运行时相同但需要在代码中区分的类型,因为你不希望开发人员将它们错误混合使用。
例如(这可能是一个愚蠢的例子):
type Username = string
type Password = string
declare function login(username: Username
在这里,我们希望确保编写调用login
的TypeScript代码的人不会意外地将密码放入用户名中,反之亦然。如果上述类型别名能够防止您这样做,那就太好了:
declare function getUsername(): Username;
declare function getPassword(): Password;
login(getPassword(), getUsername());
但实际上并没有。用户名(
Username
)和密码(
Password
)类型都只是
string
。使用不同的
名称并不会改变这个事实。因此,有时TypeScript开发人员希望他们的类型是名义上的,以捕获像上面那样的错误。
在
microsoft/TypeScript#202中有一个非常长的讨论,关于如何获得名义上的类型。一种对原始类型进行操作的方法是使用“品牌”,即添加一个“虚拟”的区分属性,该属性仅存在于类型系统中,而不是运行时。因此,您可以将上述更改为:
type Username = string & { __brand: "Username" };
type Password = string & { __brand: "Password" };
突然间,你会在这里得到所需的错误提示:
login(getPassword(), getUsername())
当然,实际上说服编译器一个特定的string
确实是一个Username
或Password
,需要通过一些类似于类型断言的方法进行欺骗:
function toUsername(x: string): Username {
return x as Username;
}
当然,在运行时你不能真正拥有Username
或Password
类型的值,因为如果你使用一个原始的string
它就不会有__brand
属性。如果编译器决定急切地将这些在运行时不可能存在的品牌类型降级为never
,它们将完全失效。这甚至比仅仅使用原始类型更糟糕,因为没有任何东西可以赋值给它们,但它们仍然是无法区分的并且容易引起混淆:
login(getPassword(), getUsername())
虽然这个特性可能不是很令人愉快,但它已经被应用于现有的 TypeScript 代码中,包括 TypeScript 编译器自身的
TypeScript 源代码。将品牌化的原语减少到
never
将会对太多人造成影响,因此不值得这样做。
游乐场链接到代码
string & number
是never
,而string & Date
是string & Date
,但我不知道你可以为string & Date
分配什么值。 - Explosion PillsObject.assign(0, new Date())
的结果... - CRiceObject.assign(0, '')
得到这个结果。 - Explosion Pills0
。Object.assign
只是欺骗类型系统,因为它的签名被设置为返回其参数类型的交集。实际产生的值不能用作Date
对象,因为它的类型会提示。不过,我仍然并不完全相信结果应该是never
,我认为通过一些原型操作可以得到既可用作数字又可用作日期的值。 - CRicestring&number
的类型视为never
:https://www.typescriptlang.org/play?#code/C4TwDgpgBAdlC8UDOwBOBLGBzKAyWArgLYBGEqA3EA - Explosion Pills