使用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