我有两种自定义的类型
type TypeA = string | number;
type TypeB = string | boolean;
我使用上述方法创建了一个路口类型。
type Combined = TypeA & TypeB;
自然,Combined
类型只会是string
类型,因为它是唯一在 TypeA
和 {{1 }} 。
但是,如果我更改 TypeB
的 union 并添加 Date 类型,则会出现意外行为,例如:
TypeB
创建新的路口类型
type TypeA = string | number;
type TypeB = string | boolean | Date;
如果我检查类型的签名,它看起来像这样
type Combined = TypeA & TypeB;
我的问题是为什么会这样?这是预期的吗?我认为它应该是 string 类型,因为它是 相交 的唯一类型。
答案 0 :(得分:3)
这不是错误;它允许使用一种称为 branded 或 tagged 原语的(未经广泛宣传的)功能。
在运行时几乎没有可能同时拥有v
(原始的string
,而不是typeof v === "string"
包装对象)的值String
Date
。从某种意义上说,类型string & Date
实际上与never
相同,因为您可以将TypeScript类型视为所有适当的JavaScript值的集合。如果没有string & Date
值,也没有never
值,则这些类型在逻辑上是等效的。
因此,如果编译器急切地将诸如string & Date
到never
的交集减小,这似乎是合理的。它已经针对"a" & 0
这样不兼容的单元类型的交集和microsoft/TypeScript#31838中实现的诸如string & number
这样的不兼容基元的交集做到了这一点。那么为什么当我们将对象类型与基元相交时为什么不发生这种情况?
原因是允许使用称为品牌基元的功能,该功能可以为TypeScript中的基元模拟nominal typing(在this FAQ entry中提到)。
TypeScript主要只有structural typing和type aliases;如果两种类型具有相同的结构但名称不同,则它们是同一类型。如果通过类型别名为现有类型赋予新名称,则它们是同一类型。通常这只是您想要的,但是有时您想提出两种类型,尽管在运行时它们是相同的,但需要在代码中加以区分,因为您不希望开发人员不小心将它们混淆。
例如(这可能是一个愚蠢的例子):
type Username = string;
type Password = string;
declare function login(username: Username; password: Password): void;
在这里,我们要确保编写调用login
的TypeScript代码的用户不会意外输入用户名的密码,反之亦然。如果上述类型别名实际上阻止您执行此操作,那就太好了
declare function getUsername(): Username;
declare function getPassword(): Password;
login(getPassword(), getUsername()); // no error, OOPS
但不是。 Username
和Password
类型都只是string
。我们使用不同的名称并不会改变这一事实。因此,有时TypeScript开发人员希望他们的类型是名义上的,以捕获上述错误。
microsoft/TypeScript#202中有很长的讨论,关于如何获得名义上的打字。对原始类型执行此操作的一种方法是使用“品牌”,在其中添加“幻像”区分属性,该属性仅在类型系统中存在,而在运行时中不存在。因此,您可以将以上内容更改为:
type Username = string & { __brand: "Username" };
type Password = string & { __brand: "Password" };
突然之间,您将在此处得到所需的错误:
login(getPassword(), getUsername()); // error! Password not assignable to Username
当然,实际上可以说服编译器某个特定的string
确实是Username
或Password
由 lying 组成,例如{{3} }:
function toUsername(x: string): Username {
return x as Username; // <-- lying
}
但是我们当然知道,在运行时,您不能真的具有类型Username
或Password
的值,因为如果您使用基本类型{{1} }它没有string
属性。如果编译器决定急切地将这些无法在运行时标记的类型减少为__brand
,则它们将完全损坏。这将比仅使用原语还要糟糕,因为没有东西可以分配给它们,但是它们仍然无法区分并允许混淆:
never
尽管此功能可能不太实用,但已在现有的真实TypeScript代码中使用,包括type assertion。将品牌基元简化为login(getPassword(), getUsername()); // no error again
会破坏太多人,使其不值得。
答案 1 :(得分:2)
这是预期的行为,原语的交集被简化为never
,而原语与对象类型的交集没有被简化(以启用诸如品牌原语类型的thig)。 Date
不是原始类型,它是lib.d.ts
鉴于此,打字稿通过移动交点内的交点来标准化并集和交点这一事实
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>;
答案 2 :(得分:1)
那是因为Date不是原始类型。不兼容的原始类型减少为<div class="formOuter">
<div class="form">
<label class="grid-1">
<input type="text">
</label>
<label class="grid-2">
<input type="text">
</label>
<label class="grid-3">
<input type="text">
</label>
<input type="submit" class="grid-4">
</div>
</div>
。
never
对于开发人员而言,无需减少调试就更容易进行调试。 您可以尝试使用自己的课程,它将具有相同的行为。
我寻找了更详细的答案,并找到了这个答案:https://stackoverflow.com/a/53545038/14438744