我很惊讶地发现 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' };
我想也许 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
,则相同。
为什么这不会产生错误?显然,我没有提供一种或另一种类型,而是提供了两者的奇怪组合!
最佳答案
问题中的讨论 Microsoft/TypeScript#14094在这里是相关的。
TypeScript 中的类型是开放的,因为对象必须至少类型描述的属性才能匹配。所以对象 { value: 7, data: 'test', note: 'hello' }
匹配类型 { value: number, data: string }
,即使它有多余的 note
属性:
const obj = { value: 7, data: 'test', note: 'hello' };
const val: sthA = obj; // okay
所以你的 c
变量确实是一个有效的 sth
。只有当它缺少联合的某些组成部分所需的所有属性时,它才会成为某物
:
// error: missing both "data" and "note"
const oops: sth = { value: 7 };
但是:当您在 TypeScript 中将新的对象文字分配给类型化变量时,它会执行 excess property checking试图防止错误。这具有在该分配期间“关闭”TypeScript 的开放类型的效果。这与您对接口(interface)类型的预期一样有效。但对于联合,TypeScript 当前(如 this comment 中所述)仅提示未出现在任何 consituents 上的属性。所以下面仍然是一个错误:
// error, "random" is not expected:
const alsoOops: sth = { value: 7, data: 'test', note: 'hello', random: 123 };
但 TypeScript 目前并没有按照您想要的严格方式对联合类型进行额外的属性检查,它会根据每个组成类型检查对象文字,并在所有这些类型中存在额外属性时发出警告。它确实用 discriminated unions 做到了这一点,如 microsoft/TypeScript#12745 中所述,但这并没有解决您的问题,因为 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;
如果您真的想要表达对象类型的排他性联合,您可以使用mapped和 conditional类型来做到这一点,通过将原始联合体变成一个新的联合体,其中每个成员通过将它们添加为类型 never
的可选属性(显示为 undefined
因为可选属性总是undefined
):
type AllKeys<T> = T extends unknown ? keyof T : never;
type Id<T> = T extends infer U ? { [K in keyof U]: U[K] } : never;
type _ExclusifyUnion<T, K extends PropertyKey> =
T extends unknown ? Id<T & Partial<Record<Exclude<K, keyof T>, never>>> : never;
type ExclusifyUnion<T> = _ExclusifyUnion<T, AllKeys<T>>;
有了它,您可以将 sth
“排除”到:
type xsth = ExclusifyUnion<sth>;
/* type xsth = {
value: number;
data: string;
note?: undefined;
} | {
value: number;
note: string;
data?: undefined;
} */
现在会出现预期的错误:
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; }' */
关于typescript - 为什么 A | B允许两者结合,我该如何防止呢?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46370222/