我想知道以下情况是否正确:

type P =
  | { foo: boolean; bar?: undefined }
  | { foo: boolean; bar: boolean; baz: boolean };
const p: P = { foo: true, baz: true };

我所期望的是,我可以为p赋值的唯一有效值是:

  1. { foo: true }
  2. { foo: true, bar: true, baz: true }

很容易说明,如果bar在这两种情况下都不是可选的,那么它就会按预期工作.

是的,我可能只从联盟的第一部分中删除bar个,但我想使用这个类型作为react props ,它必须以某种方式被"区别对待".

我注意到,在Internet上演示这一点的所有示例都不包含任何额外的字段,因此可能实现这一点的唯一方法是包含联合的两个部分中的所有字段,但具有不同的类型/选项?

推荐答案

TypeScrip中的对象类型是openextendible,而不是closedsealed或"exact"(如特征请求microsoft/TypeScript#12936中所描述的).允许对象类型的值具有该类型中未提及的额外属性.如果weren't是这种情况,则extending个接口或类将 destruct 类型兼容性:

interface Foo { bar: string; }
interface Baz extends Foo { qux: number; }
const baz: Baz = { bar: "abc", qux: 123 };
const foo: Foo = baz; // okay

有一个名为excess property checking的功能,如果您将具有额外属性的object literal赋给不需要它们的东西,它会发出警告:

const fooDirect: Foo = { bar: "abc", qux: 123 }; // error!
// --------------------------------> ~~~~~~~~
// Object literal may only specify known properties, and 
// 'qux' does not exist in type 'Foo'.

这并不是因为赋值是invalid(至少不是根据 struct 类型系统);毕竟,允许使用等价的赋值const foo: Foo = baz;.之所以会出现警告,是因为在分配之后,对qux属性的了解完全丢失了,这可能是一个错误.与const foo: Foo = baz;相比,baz之后仍然是可访问的,因此知识仍然存在.

因此,多余的属性警告不是类型安全问题;它们更像是Linter特性.这可能是一个有用的功能,但不幸的是,它会引起很多困惑,有时我认为它可能会带来更多麻烦,而不是它的价值.它让人们认为类型脚本对象类型是准确的,而事实并非如此,然后当人们通过为中间变量赋值来绕过该功能时,或者当使用不强制额外属性警告的语言的一部分时,比如复制到中间变量,或者像这个问题中使用带有可选判别式的区分联合时发生的情况,人们就会抱怨.


这意味着这里的任务:

type P =
    { foo: boolean, bar?: undefined } |
    { foo: boolean, bar: boolean, baz: boolean };

const p: P = { foo: true, baz: true };

将是valid,无论你是否收到过多的财产警告.类型系统中没有不能将{foo: true, baz: true}赋值到{foo: boolean, bar?: undefined}的内容.如果希望赋值为invalid,则需要更改P以实际禁止baz键.好吧,TypeScrip不能真正禁止键,但它确实支持the impossible never type中的optional properties,因此属性要么缺失,要么是undefined,这是相似的.就像这样:

type P =
    { foo: boolean, bar?: undefined, baz?: never} |
    { foo: boolean, bar: boolean, baz: boolean };
const p: P = { foo: true, baz: true }; // error

这为您提供了您想要的错误,即使您首先try 将其复制到某个中间变量,也应该是错误的……因为你不依赖于超额财产警告.


话虽如此,你在这里看到的行为被认为是打印脚本中的一个错误,据报道是microsoft/TypeScript#39050.当您提到受歧视unions 的相关成员中不存在的房产时,应该会出现过度房产警告.但是,当判别属性是可选的并且缺失时,似乎不会发生这种情况.因此,也许有一天,这种行为会改变为你想要的方式.

但同样要清楚的是,即使他们修复了这一点,这也只是一个可以很容易绕过的过度财产警告,正如你可以看到的那样,如果你把你的判别式改为必需的:

type P =
    { foo: boolean, bar: 0 } | { foo: boolean, bar: 1, baz: boolean };
const p: P = { foo: true, bar: 0, baz: true }; // error you wanted
// -----------------------------> ~~~~~~~~~

const indirect = { foo: true, bar: 0, baz: true } as const;
const pIndirect: P = indirect; // no error

因此,无论如何您都可能想要使用可选的-never方法.


Playground link to code

Typescript相关问答推荐

为什么AB和CD这两种类型在TypScript中可以相互分配?

为什么在将条件返回类型的字符串赋给数字时没有类型错误?

typescript抱怨值可以是未定义的,尽管类型没有定义

从typescript中的对象数组中获取对象的值作为类型'

如果请求是流,如何等待所有响应块(Axios)

如何使用一个字段输入对象,其中每个键只能是数组中对象的id属性,而数组是另一个字段?

如何以Angular 形式显示验证错误消息

返回具有递归属性的泛型类型的泛型函数

TypeScrip界面属性依赖于以前的属性-针对多种可能情况的简明解决方案

保护函数调用,以便深度嵌套的对象具有必须与同级属性函数cargument的类型匹配的键

TypeScrip:强制使用动态密钥

Angular material 无法显示通过API获取的数据

为什么特定的字符串不符合包括该字符串的枚举?

Typescript,如何在通过属性存在过滤后重新分配类型?

如何在try 运行独立的文字脚本Angular 项目时修复Visual Studio中的MODULE_NOT_FOUND

打字脚本错误:`不扩展[Type]`,而不是直接使用`[Type]`

Typescript泛型调用对象';s方法

记录的子类型<;字符串,X>;没有索引签名

什么';是并类型A|B的点,其中B是A的子类?

我是否应该在 Next.js 中的服务器组件中获取秘密数据的所有函数中使用使用服务器?