Typescript 如何定义对象类型的真正逻辑OR(结果中不混合不同的对象键)

Typescript 如何定义对象类型的真正逻辑OR(结果中不混合不同的对象键),typescript,union-types,Typescript,Union Types,例如: type TA = {a:number,b:number} type TB = {a:number,c:number,d:number} const t1:Or<TA,TB> = {a:1,b:1} // want const t2:Or<TA,TB> = {a:1,c:1,d:1} // want const t3:Or<TA,TB> = {a:1,b:1,c:1} // DON'T want TypeScript实际上认为t3是有效的。Typ

例如:

type TA = {a:number,b:number}
type TB = {a:number,c:number,d:number}
const t1:Or<TA,TB> = {a:1,b:1} // want
const t2:Or<TA,TB> = {a:1,c:1,d:1} // want 
const t3:Or<TA,TB> = {a:1,b:1,c:1} // DON'T want 
TypeScript实际上认为
t3
是有效的。TypeScript union type
|
还允许合并键对象,有时还允许合并部分对象

将每个类型放置在单个元素数组中,如下所示:

type T0 = {a:number,b:number}
type T1 = {a:number,c:number,d:number}
type Test=[T0]|[T1]
const t1:Test=[{a:1,b:1,}]  
const t2:Test=[{a:1,c:1,d:1}]  
const t3:Test=[{a:1,b:1,c:1}] // fails as desired
工作正常,但测试对象也必须放置在单个元素数组中


有什么办法可以解决这个问题吗?

为什么

在TypeScript中,
|
不是运算符*,它表示该类型是左侧和右侧类型的并集。如果你想理解为什么
{a:number,b:number}{a:number,c:number,d:number}
允许
{a:number,b:number,c:number}
,这一点很重要

当您声明一个联合时,您告诉编译器一个可分配给联合的类型应该至少可分配给它的一个成员。记住这一点,让我们从这个角度检查
{a:number,b:number,c:number}
类型

联合体的左侧成员是
{a:number,b:number}
,这意味着可分配给它的类型必须至少具有
number
类型的两个属性:
a
b
(有for对象文本的概念,但正如T.J.Crowder所说的,这不适用于联合体)。来自**:

编译器只检查是否至少存在所需的类型并与所需的类型匹配

因此,由于
{a:number,b:number,c:number}
可分配给
{a:number,b:number}
不需要更多的检查-该类型至少满足联合的一个要求。顺便说一句,这种行为完全符合逻辑OR的真值表,它类似于并集


通过将类型包装到元组来解决此问题的尝试依赖于类型参数行为。因为您将类型包装在元组中,所以编译器会将一个元素的元组彼此进行比较。显然,第三个元组与第一个元组和第二个元组不同,这会提供所需的结果


什么

实际上,您想要的是逻辑异或显示的行为:其中之一,但不是两者。除了使用(T.J.Crowder)外,还可以定义一个实用程序,将一对类型转换为“两种类型中均存在但不单独存在的所有道具”类型的并集:

类型XOR


*
|
作为运算符的概念出现在中,后来被编辑掉

**必须注意的是,这并不意味着在找到匹配项时所有检查都会短路。在这种情况下,联合的所有成员本身都是对象文本,因此对已知属性的约束仍然适用于每个成员,如果在赋值过程中存在未知属性,则会导致TS2322错误:

const t4:XOR={a:1,b:1,c:1,g:3}//对象文字只能指定已知属性

请注意,多余属性检查仅应用对象文字,因此这可能会有一些实用性,但也只是一些。例如,尽管
让x:TA={a:1,b:2,c:3}
将标记额外的
c
属性,
让y={a:1,b:2,c:3};设x:TA=y不会。对联合体a(请参阅)进行了过多的属性检查,但未实现。您可以使用,但我猜您不希望使用。此答案可能有用。请不要通过破坏您的帖子为其他人做更多工作。通过在Stack Exchange网络上发布,您已授予Stack Exchange在下不可撤销的权利,以分发该内容(即,无论您未来的选择如何)。根据堆栈交换策略,帖子的非破坏版本是分发的版本。因此,任何故意破坏行为都将恢复原状。如果您想了解有关删除帖子的更多信息,请参阅:您的引用:“编译器只检查是否至少存在所需的帖子,并与所需的类型匹配”。这适用于传递给函数的类型,在这种情况下,允许任何额外的属性。对于文字赋值(在手册的同一页的下一页),不允许有额外的属性。并集所允许的既不是这些定义中的一个也不是另一个,而是一个混合定义:一个并集操作数必须由属性的子集满足,并且如果在任何操作数中存在个别附加属性,则允许使用这些属性。(未在手册中描述)。@CraigHicks-是的,你是对的,但我只想强调,由于至少有一个成员匹配,因此不会执行额外的属性检查,以确保类型不包含来自其他工会成员的属性。同时,两个成员都是对象文字,因此它们不能自己指定额外的属性(不是因为它们是联合的一部分)。但这并不意味着这是一个特例。Re:quote-我想说的是关于接口,而不是函数参数。在任何情况下,该声明看起来都没有执行其他检查,因此这部分应该得到改进。我同意你所说的一切,并且从一开始就一直是我的惯常做法——坦率地说,我不知道你为什么认为我需要在如此重复的系统上得到提醒。也就是说,我也觉得让提问者彻底解释他们的情况(不一定指这个问题),然后指向canonical可能是有益的/dupe@OlegValter-好吧,原因不言而喻-你在这里而不是那里发布了答案。但不管怎样,这些东西都是可以解释的,帮助别人总是好的,等等(无论如何,我对SO的系统有严重的怀疑。:-)快乐的编码@T.J.Crowder——如果我在回答时知道他们是被愚弄的目标,我会在那里张贴(或者可能不会——因为事实证明,我在几年前就达到了与其他人相同的技术):)Q&A熵率如此之高,令人震惊。看到这条评论我很惊讶-谢谢,哈普
type T0 = {a:number,b:number}
type T1 = {a:number,c:number,d:number}
type Test=[T0]|[T1]
const t1:Test=[{a:1,b:1,}]  
const t2:Test=[{a:1,c:1,d:1}]  
const t3:Test=[{a:1,b:1,c:1}] // fails as desired