作为头衔.我买了一个在线课程,我直到现在都没有时间看.在课程中,它说了类似于TypeScript中不允许的was:

type Name = { name: string }
type Age = { age: number }

type UnionBoth = Name | Age

const test = (union: UnionBoth): void => {
    if ('name' in union && 'age' in union) {
        union.name
        union.age
    }
}

但在当前版本5.4.3上测试后,它没有出现错误.我开始在上一个版本上测试相同的代码并重复它,最后,我在4.8.4处达到了an error on the Playground:

Property 'name' does not exist on type 'never'.
Property 'age' does not exist on type 'never'.

有什么解释吗?这门课程试图"改变"我的思维模式,说unions 可能不是我们认为的the intuitive way.现在这不是错误,那么正确的心理模型是什么,正确地理解上面的代码?


只是判断了5.4.3处的代码,函数中的推理类型union是以下类型:

(parameter) union: Name & Record<"age", unknown>

为什么这里有Record只为age房地产?


我目前的心理模型:

  1. 有两组NameAge.
  2. 函数的联合union可以落入以下三种情况之一:
    1. 只在Name.
    2. 只在Age.
    3. 它在十字路口,即Name & Age.
  3. 通过if语句的类型缩小,TypeScript应该理解大小写是3,我们应该能够访问块内的nameage.

推荐答案

TypeScript总是有in operator narrowing,所以if ("name" in val)将用于将{name: string} | {age: number}缩小到{name: string}. TypeScript 4.9引入了unlisted property narrowing with the in operator,因此if ("age" in val){name: string}缩小为{name: string; age: unknown}. 是的,这些特征是相互不一致的. 您可以阅读microsoft/TypeScript#50666处的实现拉取请求和microsoft/TypeScript#21732处的特征请求,讨论人们希望in操作员如何工作,以及所涉及的权衡.

没有一个在所有情况下都有效的"正确"心理模型.TypeScript试图支持惯用的JavaScript,尽管它实际上并没有整合成一个整体.TypeScript的类型系统故意设置为not sound,以使开发人员能够高效生产. 在其他条件相同的情况下,健全性比不健全性更受青睐,但开发人员的生产力有时更重要.(见第TypeScript Design Non-Goal #3章)

TypeScript usually将对象类型视为开放/可扩展,而不是封闭/密封/精确.因此,值{a: "x", b: "y"}可赋给{a: string},尽管它的属性超过了b.但这些东西也是错误的原因,所以TypeScript有excess property checksin运算符缩小.


对于in操作符的目的,我会说心理模型是以下认知失调:如果你使用in来判断存在于大约union个成员中的属性,TypeScript会将这些联合类型视为闭合/密封/精确,并消除其他成员(即使这在技术上是不合理的).另一方面,如果你使用in来判断一个不存在于任何联合成员中的属性,那么它将该类型视为开放/可扩展的类型,并只向该对象类型添加一个unknown类型的属性.

当你写的时候,

const test = (union: UnionBoth): void => {
    if ('name' in union && 'age' in union) {
        union.name
        union.age
    }
}

发生的事情是:首先你判断'name' in union,它将unionUnionBoth缩小到{name: string},1union04. 然后你判断'age' in union,它将union{name: string}缩小到{name: string, age: unknown}. 这完全符合上面的心理模型,导致了这里坦率地奇怪的情况.如果你改变你的in个测试的顺序,你会得到相反的奇怪的东西.

在我看来,这表明的是:几乎没有人像你现在这样使用联合类型.如果他们用in来判断属性,那是因为他们试图用discriminate一个unions ,即使这样做是不合理的. 如果你真的想做这种判断,你需要重构你的联合类型,以明确地说明事情是如何工作的:

type UnionBoth = Name | Age | (Name & Age)

const test = (union: UnionBoth): void => {
    if ('name' in union && 'age' in union) {
        union;
        // ^? Name & Age
        union.name
        //    ^? string
        union.age
        //    ^? number
    }
}

现在明确提到了Name & Age的可能性,TypeScript使用其原始的in缩小到Name & Age,从不使用新的"未列出的属性缩小",因为总有一个联合成员包含您要测试的键.

Playground link to code

Typescript相关问答推荐

如何在另一个if条件中添加if条件

有没有可能使用redux工具包的中间件同时监听状态的变化和操作

使用动态主体初始化变量

如何在另一个参数类型中获取一个参数字段的类型?

如何在Angular 12中创建DisplayBlock组件?

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

被推断为原始类型的交集的扩充类型的交集

在排版修饰器中推断方法响应类型

如何在Vue中使用Enum作为传递属性的键类型?

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

如何缩小函数的返回类型?

API文件夹内的工作员getAuth()帮助器返回:{UserID:空}

如何在具有嵌套数组的接口中允许新属性

如何在本地存储中声明该类型?

为什么看起来相似的代码在打字时会产生不同的错误?

有没有一种方法可以防止我的组件包在其中安装样式组件'; node 模块中的s目录

如何使对象同时具有隐式和显式类型

定义一个只允许来自另一个类型的特定字符串文字的类型的最佳方式

Angular 16:无法覆盖;baseUrl;在tsconfig.app.json中

Typescript 确保通用对象不包含属性