Typescript 类型脚本a | b允许两者的组合

Typescript 类型脚本a | b允许两者的组合,typescript,xor,typescript-types,Typescript,Xor,Typescript Types,我惊讶地发现TypeScript不会抱怨我做了这样的事情: type sth = { value: number, data: string } | { value: number, note: string }; const a: sth = { value: 7, data: 'test' }; const b: sth = { value: 7, note: 'hello' }; const c: sth = { value: 7, data: 'test', note: 'hello'

我惊讶地发现TypeScript不会抱怨我做了这样的事情:

type sth = { value: number, data: string } | { value: number, note: string };
const a: sth = { value: 7, data: 'test' };
const b: sth = { value: 7, note: 'hello' };
const c: sth = { value: 7, data: 'test', note: 'hello' };
import { XOR } from 'ts-essentials';

type sth = XOR<
  { value: number, data: string; },
  { value: number, note: string; }
>;

const a: sth = { value: 7, data: 'test' };
const b: sth = { value: 7, note: 'hello' };
const c: sth = { value: 7, data: 'test', note: 'hello' };
// ~ Type '{ value: number; data: string; note: string; }'
//     is not assignable to type ...
我想可能
value
被选为类型联合判别式之类的东西,因为我唯一能解释这一点的是,如果TypeScript不知何故理解
number
这里是
1 | 2
的超集

因此,我将第二个对象上的
value
更改为
value2

type sth = { value: number, data: string } | { value2: number, note: string };
const a: sth = { value: 7, data: 'test' };
const b: sth = { value2: 7, note: 'hello' };
const c: sth = { value: 7, data: 'test', note: 'hello' };
不过,没有抱怨,而且我能够构建
c
。IntelliSense在
c
上出现故障,但当我
进入它时,它不会给出任何建议。如果我将
c
中的
value
更改为
value2
,则相同

为什么这不会产生错误?显然,我没有提供一种或另一种类型,而是提供了两者的奇怪组合

所讨论的问题与此相关

TypeScript中的类型是开放的,因为对象必须至少具有类型所描述的属性才能匹配。因此,对象
{value:7,data:'test',note:'hello'}
匹配类型
{value:number,data:string}
,即使它有多余的
note
属性。所以你的
c
变量确实是一个有效的
sth
。如果它缺少工会某些成员所要求的所有属性,那么它就不能成为一个
sth

// error: missing both "data" and "note"
const oops: sth = { value: 7 };  
type sth =
  { value: number, data: string; note?: never; } |
  { value: number, note: string; data?: never; };

const a: sth = { value: 7, data: 'test' };
const b: sth = { value: 7, note: 'hello' };
const c: sth = { value: 7, data: 'test', note: 'hello' };
   // ~ Type '{ value: number; data: string; note: string; }'
   //     is not assignable to type 'sth'.
但是:当您在TypeScript中将一个新的对象文本分配给一个类型化变量时,它会执行以防止错误。这具有在该分配期间“关闭”TypeScript的打开类型的效果。这与您期望的接口类型一样有效。但对于联合体,TypeScript目前(如中所述)只抱怨在任何联合体上都没有出现的属性。因此,以下仍然是一个错误:

// error, "random" is not expected:
const alsoOops: sth = { value: 7, data: 'test', note: 'hello', random: 123 };
但是TypeScript目前并没有严格按照您想要的方式对联合类型执行多余的属性检查,它根据每个组成类型检查对象文本,并抱怨是否所有类型中都有额外的属性。它确实是这样做的,但这并不能解决您的问题,因为
sth
的定义都没有区别(意思是:拥有一个属性,其文字类型恰好选择了联合的一个组成部分)


因此,除非更改此选项,否则最好的解决方法可能是在使用对象文字时避免联合,方法是显式指定给预期的成分,然后在需要时扩大到联合:

type sthA = { value: number, data: string };
type sthB = { value: number, note: string };
type sth = sthA | sthB;

const a: sthA = { value: 7, data: 'test' };
const widenedA: sth = a;
const b: sthB = { value: 7, note: 'hello' };
const widenedB: sth = b;
const c: sthA = { value: 7, data: 'test', note: 'hello' }; // error as expected
const widenedC: sth = c; 
const cPrime: sthB = { value: 7, data: 'test', note: 'hello' }; // error as expected
const widenedCPrime: sth = cPrime; 

如果确实要表示对象类型的独占联合,可以使用和类型来实现,方法是将原始联合转换为新的联合,其中每个成员通过将其作为类型为
never
的可选属性添加,明确禁止来自联合其他成员的额外键(显示为
未定义
,因为可选属性始终可以是
未定义
):

现在将出现预期的错误:

const z: xsth = { value: 7, data: 'test', note: 'hello' }; // error!
/* Type '{ value: number; data: string; note: string; }' is not assignable to
 type '{ value: number; data: string; note?: undefined; } | 
 { value: number; note: string; data?: undefined; }' */


另一个选项是使用可选的
never
属性明确禁止从联合中的两种类型混合使用字段:

// error: missing both "data" and "note"
const oops: sth = { value: 7 };  
type sth =
  { value: number, data: string; note?: never; } |
  { value: number, note: string; data?: never; };

const a: sth = { value: 7, data: 'test' };
const b: sth = { value: 7, note: 'hello' };
const c: sth = { value: 7, data: 'test', note: 'hello' };
   // ~ Type '{ value: number; data: string; note: string; }'
   //     is not assignable to type 'sth'.
ts essentials
库中有一个可用于帮助您构建以下独占联合:

type sth = { value: number, data: string } | { value: number, note: string };
const a: sth = { value: 7, data: 'test' };
const b: sth = { value: 7, note: 'hello' };
const c: sth = { value: 7, data: 'test', note: 'hello' };
import { XOR } from 'ts-essentials';

type sth = XOR<
  { value: number, data: string; },
  { value: number, note: string; }
>;

const a: sth = { value: 7, data: 'test' };
const b: sth = { value: 7, note: 'hello' };
const c: sth = { value: 7, data: 'test', note: 'hello' };
// ~ Type '{ value: number; data: string; note: string; }'
//     is not assignable to type ...
从“ts essentials”导入{XOR};
输入某物=XOR<
{value:number,data:string;},
{值:数字,注意:字符串;}
>;
常数a:sth={value:7,数据:'test'};
常数b:sth={value:7,注意:'hello'};
常量c:sth={value:7,数据:'test',注释:'hello'};
//~Type'{value:number;data:string;note:string;}'
//不可分配给类型。。。

这是最后一个例子的例子。

这个答案解决了如何计算文本初始值设定项赋值的有效性,例如,
{value:7,data:'test',note:'hello'}
到对象类型的联合,例如,
type sth={value:number,data:string}{value:number,note:string}
不忽略任何未指定的多余属性

此处提供的类型函数可与中的
ExclusifyUnion
相比较。但是,它不仅仅是另一个类型函数,它使用相同的输入,但编码略有不同。相反,此处提供的函数使用额外的输入,如下所述

将文本初始值设定项的类型作为额外参数添加到类型函数 考虑以下陈述:

type T1 = {<some props>}
type T2 = {<some props>}
type T3 = {<some props>}
type TU=T1|T2|T3
SomeTypeDef<T> = ...
const t:SomeTypeDef<TU> = {a:1,b:2}
您会注意到赋值的r.h.s.上的文字初始值设定项的类型。现在假设我们将该类型作为附加变量添加到l.h.s.上的类型函数中:

const t:SomeTypeDefPlus<TU,I> = {a:1,b:2}

讨论 当属性本身是对象时,代码对deep object-
ExclusifyUnionPlus
调用
Select
Select
递归调用
ExclusifyUnionPlus

一些边缘情况不包括在内,例如成员函数

测验 测试用例包括

  • 附加钥匙
  • 深度对象(不过只有两层)
结论 除了两次输入实例的要求外,所提出的范例(向lhs函数添加初始值设定项类型)在几个检测多余属性的测试用例中都能正确运行

根据以下两个标准,我们可以通过比较和
排除UnionPlus
来判断向l.h.s.类型函数添加初始值设定项类型的实用价值:

  • 简单明了:
  • 表达式的总范围:
至于“易用性和清晰性”,排除UnionPlus似乎更容易编码和理解。另一方面,编写两次初始值设定项很不方便。我已提交建议,类似

const t:SomeTypeDefPlus<TU,I> = {a:1,b:2} as infer literal I

const t:SomeTypeDefPlus={a:1,b:2}作为推断文字I
会有帮助的


至于“表达式的总范围”,这还不清楚。

我不明白,在
const t:SomeTypeDefPlus<TU,I> = {a:1,b:2} as infer literal I