使用Typescript的交集类型
我有两种自定义类型使用Typescript的交集类型,typescript,types,intersection,Typescript,Types,Intersection,我有两种自定义类型 type TypeA = string | number; type TypeB = string | boolean; 我使用上述方法创建一个交叉点类型 type Combined = TypeA & TypeB; 自然地,组合的类型将只属于字符串,因为它是类型A和类型B之间唯一相交的类型 但是,如果我更改类型b的并集,并添加一个日期类型,我会得到一个意外的行为,例如: type TypeA = string | number; type TypeB = str
type TypeA = string | number;
type TypeB = string | boolean;
我使用上述方法创建一个交叉点类型
type Combined = TypeA & TypeB;
自然地,组合的
类型将只属于字符串
,因为它是类型A
和类型B
之间唯一相交的类型
但是,如果我更改类型b的并集,并添加一个日期类型,我会得到一个意外的行为,例如:
type TypeA = string | number;
type TypeB = string | boolean | Date;
创建新的交点类型
type Combined = TypeA & TypeB;
如果我检查类型的签名,它看起来像这样
type Combined = string | (string & Date) | (number & Date)
我的问题是为什么会这样?这是预期的吗?我认为它应该是字符串类型,因为它是唯一一个与相交的类型这是预期的行为,基本体的相交被简化为从不
,而基本体与对象类型的相交则不被简化(以启用标记的基本体类型). <代码>日期
不是基元,它是lib.d.ts中定义的对象类型
给出这一点,typescript通过将交集移到我们得到的内部来规范化并集和交集
type TypeA = string | number;
type TypeB = string | boolean;
type Combined = TypeA & TypeB;
=> (string | number) & (string | boolean)
// Distributivity kicks in
=> (string & string) | (string & boolean) | (number & string) | (number & boolean)
// intersection simplification
=> string | never | never | never
// never melts away in a union
=> string
而在第二种情况下,我们得到
type TypeA = string | number;
type TypeB = string | boolean | Date;
type Combined = TypeA & TypeB;
=> (string | number) & (string | boolean | Date)
// Distributivity kicks in
=> (string & string) | (string & boolean) | (string & Date) | (number & string) | (number & boolean) | (number & Date)
// intersection simplification, but nothing is done about Date and any primitive
=> string | never | (string & Date) never | never | (number & Date)
// never melts away in a union
=> string
如果要从TypeA
中提取TypeB
中存在的任何类型,最好使用extract
条件类型
type TypeA = string | number;
type TypeB = string | boolean | Date;
type Combined = Extract<TypeA, TypeB>;
type TypeA=string | number;
类型B=字符串|布尔值|日期;
类型组合=提取;
这是因为日期不是基本类型。不兼容的基元类型减少为
从不
type TypeA = string | number;
type TypeB = string | boolean;
type Combined = TypeA & TypeB;
// string | (string & number) | (string & boolean)
// => string | never | never
// => string
对于开发人员来说,在没有这种减少的情况下进行调试更容易。
您可以尝试使用自己的类,它将具有相同的行为
我寻找了一个更详细的答案,发现了这个:这不是一个bug;它允许一种(未被广泛宣传的)称为品牌或标记原语的功能
在运行时几乎不可能有一个值v
,它既是字符串
(原语,其中typeof v==“string”
,而不是字符串
包装对象)又是日期
。从某种意义上说,类型string&Date
实际上与never
相同,因为您可以将TypeScript类型视为所有适当JavaScript值的集合。如果没有string&Date
值,并且没有never
值,那么这些类型在逻辑上是等价的
因此,如果编译器急切地将像string&Date
这样的交集减少到never
,这是有道理的。它已经对不兼容单元类型(如“a”&0
)的交点以及不兼容原语(如字符串和数字)的交点执行了此操作,如中所实现的。那么,当我们将对象类型与基本体相交时,为什么不会发生这种情况呢
其原因是允许一个名为品牌原语的功能,该功能模拟TypeScript中的原语(在中提到)
打字稿大多只有和;如果两种类型具有相同的结构但名称不同,则它们是相同的类型。如果通过类型别名为现有类型指定新名称,则它们是相同的类型。通常这正是您想要的,但有时您希望提出两种类型,虽然在运行时相同,但需要在代码中加以区分,因为您不希望开发人员意外地将它们混淆
例如(这可能是一个愚蠢的例子):
在这里,我们希望确保编写调用login
的TypeScript代码的人不会意外地为用户名输入密码,反之亦然。如果上述类型别名实际上阻止您执行此操作,那就太好了:
declare function getUsername(): Username;
declare function getPassword(): Password;
login(getPassword(), getUsername()); // no error, OOPS
但事实并非如此。用户名
和密码
类型都只是字符串
。事实上,我们使用不同的名称并没有改变这一点。因此,有时候TypeScript开发人员希望他们的类型是标称的,以捕获上述错误
type Combined = TypeA & TypeB;
关于如何获得命名类型,在中有一个很长的讨论。对基元类型执行此操作的一种方法是使用“品牌”,即添加一个仅存在于类型系统中而不存在于运行时的“幻影”区别属性。因此,您可以将上述内容更改为:
type Username = string & { __brand: "Username" };
type Password = string & { __brand: "Password" };
突然你会在这里得到想要的错误:
login(getPassword(), getUsername()); // error! Password not assignable to Username
当然,让编译器相信一个特定的字符串
实际上是一个用户名
或密码
包括通过以下方式说谎:
虽然这个特性可能不是很有趣,但它已经被用于现有的现实世界的TypeScript代码中,包括。将品牌原语缩减为永不
,会让太多的人感到不值得
这可能是TypeScript中的一个错误,但是字符串和数字
是从不
,而字符串和日期
是字符串和日期
,但我不知道您可以为字符串和日期
指定什么值,这是一个错误吗?在我看来(尽管可能很奇怪),实际上十字路口并不是空的。例如,考虑表达式<代码>对象的结果。赋值(0,新数据())< /代码>……CRice不确定,但似乎只有0。你也可以通过Object.assign(0,”)
是的,我认为它只是0
。Object.assign
只是在欺骗类型系统,因为它的签名被设置为返回其参数类型的交集。产生的实际值不能像其类型所建议的那样用作Date
对象。我仍然不完全相信结果应该是从不
,不过,我认为通过一些原型操作,我可以得到一个既可用作数字又可用作日期的值。我只是说,TypeScript考虑了字符串和数字的类型
function toUsername(x: string): Username {
return x as Username; // <-- lying
}
login(getPassword(), getUsername()); // no error again