代码之家  ›  专栏  ›  技术社区  ›  NeoZoom.lua

我们过去无法从TypeScript中的联合一次访问所有属性?

  •  2
  • NeoZoom.lua  · 技术社区  · 2 年前

    作为标题。我买了一个在线课程,直到现在我都没有时间看。当然,上面写着这样的话 TypeScript中不允许:

    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 ,它工作时不会出错。我开始在以前的版本上测试相同的代码,并重复它 an error on the Playground 4.8.4 :

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

    有什么解释吗?课程试图“改变”我的思维模式,说工会可能不会 直观的方式 我们认为。现在这不是一个错误,那么正确理解上面代码的正确心理模型是什么?


    刚刚检查了代码 5.4.3 的推理类型 union 中的函数类型为:

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

    为什么有 Record 这里只是为了房子 age ?


    我目前的心理模型:

    1. 有两套 Name Age .
    2. 工会 协会 的函数可以分为以下三种情况之一:
      1. 它在 名称 只有
      2. 它在 年龄 只有
      3. 它在十字路口,即。 Name & Age .
    3. 通过的类型缩小 if 语句,TypeScript应该理解情况是3,并且我们应该能够访问这两个 name 年龄 在街区内。
    1 回复  |  直到 2 年前
        1
  •  2
  •   jcalz    2 年前

    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 讨论人们希望如何 在里面 操作员的工作,以及所涉及的权衡。

    没有一种“正确”的心理模式在任何情况下都有效。TypeScript试图支持惯用的JavaScript,尽管这实际上并不是一个整体。TypeScript的类型系统是有意的 not sound ,以使开发人员能够富有成效。在其他条件相同的情况下,健全性比不健全性更受欢迎,但开发人员的生产力有时更重要。看见 TypeScript Design Non-Goal #3 .)

    TypeScript 通常 将对象类型视为打开/可扩展的,而不是关闭/密封/精确的。所以价值 {a: "x", b: "y"} 可分配给 {a: string} 尽管它过剩 b 所有物但这些事情也是导致错误的原因,所以TypeScript有 excess property checks 在里面 运算符变窄。


    为了 在里面 操作员,我想说心理模型是以下认知失调:如果你使用 在里面 检查某些中存在的属性 union 成员,TypeScript将把这些联合类型视为封闭/密封/精确的,并消除其他成员(即使这在技术上是不健全的)。另一方面,如果您正在使用 在里面 要检查任何联合成员中都不存在的属性,它会将该类型视为开放/可扩展类型,并只添加一个属性 unknown 类型转换为该对象类型。

    因此,当你写作时

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

    结果是:首先你检查 'name' in union 它变窄了 union 从…起 UnionBoth {name:string} , 消除 {age: number} 。然后检查 'age' in union 它变窄了 协会 从…起 {name:string} {name: string, age: unknown} 。这完全符合上述心理模式,导致了这里坦率地说很奇怪的情况。如果您切换的顺序 在里面 测试,你会得到相反的奇怪结果。

    在我看来,这表明:几乎没有人像你现在这样使用工会类型。如果他们正在检查属性 在里面 ,是因为他们试图 区别对待 一个联合,即使这样做是不合理的。如果你真的想进行这种检查,你需要重构你的联合类型,以明确说明事情是如何工作的:

    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使用其原始 在里面 缩小到缩小到 名称&年龄 ,永远不要使用新的“未列出的属性缩小”,因为总有一个联盟成员包含您要测试的密钥。

    Playground link to code

    推荐文章