为什么 A |B 允许两者结合,我该如何预防? [英] Why does A | B allow a combination of both, and how can I prevent it?

查看:24
本文介绍了为什么 A |B 允许两者结合,我该如何预防?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I was surprised to find that TypeScript won't complain at me doing something like this:

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 例如.

I thought maybe value was picked out as a type union discriminant or something, because the only thing that I could come up with to explain this was if TypeScript somehow understood number here to be a superset of 1 | 2 for example.

所以我在第二个对象上将 value 改为 value2 :

So I changed value to be value2 on the second object:

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.

Still, no complaint, and I'm able to construct c. IntelliSense breaks down on c though, it won't suggest anything when I . into it. Same if I change value in c to be value2.

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

Why doesn't this produce an error? Clearly, I have failed to provide one type or the other and instead provided a weird mix of both!

推荐答案

issue 中的讨论 Microsoft/TypeScript#14094 与此处相关.

The discussion in issue Microsoft/TypeScript#14094 is relevant here.

TypeScript 中的类型是开放,因为对象必须至少具有类型描述的属性才能匹配.所以对象 { value: 7, data: 'test', note: 'hello' } 匹配类型 { value: number, data: string },即使它有多余的 note 属性.所以你的 c 变量确实是一个有效的 sth.如果它缺少联合的某些组成部分所需的所有属性,它只会失败为sth:

Types in TypeScript are open in the sense that an object has to have at least the properties described by a type for it to match. So the object { value: 7, data: 'test', note: 'hello' } matches the type { value: number, data: string }, even though it has that excess note property. So your c variable is indeed a valid sth. It would only fail to be a sth if it were missing all properties required by some constituent of the union:

// error: missing both "data" and "note"
const oops: sth = { value: 7 };  

但是:当您在 TypeScript 中为类型化变量分配新的对象文字时,它会执行 过多的属性检查以防止错误.这具有关闭"的效果.TypeScript 在该任务期间的开放类型.这与您对接口类型的期望一样有效.但是对于工会,TypeScript 目前(如此评论中所述)只抱怨关于未出现在任何 成分中的属性.所以下面还是报错:

However: when you are assigning a fresh object literal to a typed variable in TypeScript, it performs excess property checking to try to prevent errors. This has the effect of "closing" TypeScript's open types for the duration of that assignment. This works as you expect for interface types. But for unions, TypeScript currently (as mentioned in this comment) only complains about properties that don't appear on any of the consituents. So the following is still an error:

// error, "random" is not expected:
const alsoOops: sth = { value: 7, data: 'test', note: 'hello', random: 123 };

但是 TypeScript 目前并没有以您想要的严格方式对联合类型进行过多的属性检查,它会根据每个组成类型检查对象字面量,并在所有这些类型中都有额外的属性时发出抱怨.它通过歧视联合来做到这一点,但这并没有解决你的问题,因为这两个定义都没有sth 被区分(意思是:有一个属性,它的字面类型正好选择联合的一个组成部分).

But TypeScript currently doesn't do excess property checking on union types in the strict way that you want, where it checks the object literal against each constituent type and complains if there are extra properties in all of them. It does do this with discriminated unions, but that doesn't address your issue because neither definition of sth is discriminated (meaning: having a property whose literal type picks out exactly one constituent of the union).

因此,除非更改此设置,否则最好的解决方法可能是在使用对象字面量时避免联合,方法是显式分配给预期的组成部分,然后根据需要扩展到联合:

So, until and unless this is changed, the best workaround for you is probably to avoid unions when using object literals by assigning explicitly to the intended constituent and then widening to the union later if you want:

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; 


如果您真的想要表达一个排他对象类型的联合,您可以使用映射conditional 类型这样做,通过将原始联合转换为新联合,其中每个成员通过将它们添加为类型的可选属性来明确禁止联合其他成员的额外键never(显示为 undefined 因为可选属性总是可以是 undefined):


If you really want to express an exclusive union of object types, you can use mapped and conditional types to do so, by turning the original union into a new one where each member explicitly prohibits extra keys from the other members of the union by adding them as optional properties of type never (which shows up as undefined because optional properties can always be 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 变成:

Armed with that, you can "exclusify" sth into:

type xsth = ExclusifyUnion<sth>;
/* type xsth = {
    value: number;
    data: string;
    note?: undefined;
} | {
    value: number;
    note: string;
    data?: undefined;
} */

现在会出现预期的错误:

And now the expected error will appear:

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; }' */


代码的游乐场链接

这篇关于为什么 A |B 允许两者结合,我该如何预防?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆