gpt4 book ai didi

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

转载 作者:行者123 更新时间:2023-12-05 06:54:01 27 4
gpt4 key购买 nike

我很惊讶地发现 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/65654839/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com