typescript - 为什么 A | B允许两者结合,我该如何防止呢?

标签 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' };

我想也许 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; 

如果您真的想要表达对象类型的排他性联合,您可以使用mappedconditional类型来做到这一点,通过将原始联合体变成一个新的联合体,其中每个成员通过将它们添加为类型 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; }' */

Playground link to code

关于typescript - 为什么 A | B允许两者结合,我该如何防止呢?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46370222/

相关文章:

Mysql:用键对字符串进行异或

assembly - XOR寄存器,寄存器(汇编器)

typescript :从类型中排除所有必需的属性

typescript - 为什么类型 "never"在联合类型中毫无意义?

reactjs - react typescript 和 useCallback

javascript - 如何手动抛出可观察到的错误?

typescript - 嵌套元组传播分布率

typescript - 角 6 : How to unsubscribe rsjx interval or timer?

JavaScript 的异或结果与 Java 的结果不同

typescript - 扩展@types - 从接口(interface)中删除字段,将类型添加到接口(interface)中的字段