【问题标题】:TypeScript: Returned type when accessing undefined key on object is wrong?TypeScript:访问对象上未定义的键时返回的类型是错误的?
【发布时间】:2020-04-29 02:48:37
【问题描述】:

在 TypeScript 中使用带有可变键的对象时,我注意到了意外的类型。

给定以下代码

type MyType = {
  [x: number] : number
}

const o : MyType = {
  0: 1,
}

const a = o[0]; // Return type correctly identified as number
const b = o[1]; // Return type should be undefined and compiler should not allow this

我注意到,当使用变量键的[x: number] 语法定义对象类型时,无法正确检测使用[...] 的对象访问类型。

VSCode 告诉我ab 都具有number 类型。类型不应该是number | undefined,因为它可能会发生未定义键的情况?将ab 记录到控制台时,anumber,而bundefined

MyType 类型的对象被传递给一个函数并且该函数访问该对象中的一个键然后执行以下操作时,问题变得更加严重:

function fun(o: MyType) : number {
   return o[10000] ?? null  // can be number or null at runtime
}

没有显示错误。但是当代码运行时,o[10000] 是未定义的,因此返回值是null,它不是一个有效的数字。想象一下使用这个返回值来执行进一步的计算,这些计算都是基于一个有效数字的假设。这会导致编译器应该检测到的运行时错误(如果我不理解完全错误的东西,我是 TS 新手)

(这些示例中使用的 TypeScript 是由 create-react-app 配置的,我没有更改任何设置)

【问题讨论】:

    标签: javascript typescript types


    【解决方案1】:

    类型不应该是数字吗? undefined 而是因为它可能会发生未定义键的情况?

    如果你愿意,你可以选择;只需将其定义为:

    type MyType = {
      [x: number] : number | undefined
    }
    

    如果您希望访问可能不存在的随机属性,这可能会很有用。

    但另一方面,以更规范的方式使用对象是相当普遍的,以避免访问不存在的属性的可能性。例如,如果您通常编写使用Object.keys(o)for ... in 来决定要访问哪些属性的代码,那么您已经在做确保它存在的工作,并且总是让打字稿告诉您可能会令人沮丧你它可能是未定义的。

    【讨论】:

    • 如果有人想要对此的“官方”答案,请参阅microsoft/TypeScript#13778,请求在索引签名值域中自动包含undefined,以及cmets(this 和@987654323 @) 来自 TS 团队的领导,解释了为什么他们认为这不是一个好主意(它会经常被解决,以至于它实际上不会在实践中提供安全性)
    • 感谢您的链接。我阅读了大多数 cmets,我认为默认类型应该包括 undefined。主要原因是您可能忘记自己添加undefined。此外,您不希望在forEachentries 中有| undefined,因为在这种情况下,您知道它不是未定义的。 github问题中的大多数人似乎都同意我的观点。遗憾的是 TS 团队不想更改它,因为它是许多运行时错误的原因,而且类型是错误的。我开始使用 TypeScript 来获得类型安全
    • @Nicholas Tower:这只是部分解决方案,因为这也意味着当我真的只想分配数字时,允许我的代码将 undefined 分配给一个键。比较 TS 中 Maps 的定义。这就是普通对象的行为。我认为可以通过在索引操作类型中使用undefined 来避免许多运行时错误。但遗憾的是,TS 团队不想改变这一点……
    【解决方案2】:

    "noUncheckedIndexedAccess": true 添加到您的 tsconfig.json 编译器选项对象

    【讨论】:

      【解决方案3】:

      您的MyType 定义了一个索引访问器,它允许将任何数字用作对象的访问器:

      type MyType = {
        [x: number] : number
      }
      

      这意味着,就类型安全而言,任何数字都可用于使用索引访问器访问对象,类似于数组。

      您的对象只定义了一个属性 - 但类型检查器(编译时间)不会查看您的对象,它是在运行时创建的。类型检查器只知道MyType,任何数字都是有效值,可用于索引类型为MyType 或扩展为MyType 的任何对象。

      您必须记住,您的对象在运行时仍然只是一个常规的旧 JavaScript 对象,一旦代码编译并运行,TypeScript 就不再适用。

      如果您要定义具有一组属性的类型,那么您应该使用一组属性来构造您的类型,而不是使用索引访问器:

      interface MyType {
        0: number
      }
      
      const o: MyType = { 0: 1 } // valid
      const o2: MyType = { 1: 1 } // type checking error
      o[0] // valid
      o[1] // type checking error 
      

      【讨论】:

      • 硬编码所有可能的键通常是不可能的,例如当它们由用户输入确定时。 TS 编译器现在假定在使用 [key: number] : T 定义时所有可能的数字都是定义的键。但实际上这不可能是真的,因为有无数个可能的键。一个常见的错误是从对象访问无效键,编译器当前未检测到该键。所以我认为这会导致许多可以轻松避免的错误。 TS 中的 Map 类型正确,只有普通对象会错误
      • 我想我不明白你期望 TypeScript 能够对结构在运行时定义的数据做什么。当您使用不存在的键调用 .get() 时,即使 Map 对象也会返回 undefined - 这不是错误。
      • 没错!这就是我所期望的。返回类型必须包括undefined(就像Map.get 一样),但是对具有[key: string] : SomeType 类型的props 的普通对象的索引访问没有undefined 包含在导致许多错误的类型中。类型只是SomeType,但应该是SomeType | undefined,因为并非所有键都存在。
      • 哦!您也许可以配置它。尝试在您的tsconfig.json 中设置"strict": true,然后从那里开始。供参考:typescriptlang.org/docs/handbook/compiler-options.html -- 特别是strictNullChecks
      • 开启该功能后,您应该在访问映射或哈希对象时显式检查 null / undefined
      猜你喜欢
      • 2021-02-05
      • 2018-09-19
      • 2015-09-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-01-27
      • 1970-01-01
      • 2016-12-02
      相关资源
      最近更新 更多