【问题标题】:Typescript: Index signature is missing in type打字稿:类型中缺少索引签名
【发布时间】:2022-03-08 19:29:18
【问题描述】:

我想让MyInterface.dic像字典一样name: value,我定义如下:

interface MyInterface {
    dic: { [name: string]: number }
}

现在我创建一个等待我的类型的函数:

function foo(a: MyInterface) {
    ...
}

然后输入:

let o = {
    dic: {
        'a': 3,
        'b': 5
    }
}

我期待foo(o) 是正确的,但编译器正在下降:

foo(o) // Typescript error: Index signature is missing in type { 'a': number, 'b': number }

我知道有一个可能的转换:let o: MyInterface = { ... } 可以解决问题,但问题是,为什么 typescript 无法识别我的类型?


额外:如果 o 被声明为内联,则可以正常工作:

foo({ 
  dic: {
    'a': 3, 
    'b': 5
  }
})

【问题讨论】:

标签: casting typescript


【解决方案1】:

问题是,当推断类型时,那么o的类型是:

{ dic: { a: number, b: number } }

这与{ dic: { [name: string]: number } } 不同。至关重要的是,您不能使用顶部签名执行o.dic['x'] = 1 之类的操作。你是第二个签名。

它们在运行时是等价的类型(实际上,它们的值完全相同),但 TypeScript 的安全性很大一部分来自于它们并不相同,它只会让你处理对象作为字典,如果它知道它明确地打算作为一个字典。这就是阻止您意外读取和写入对象上完全不存在的属性的原因。

解决方案是确保 TypeScript 知道它是用作字典的。这意味着:

  • 在某处明确提供一个类型,告诉它它是一个字典:

    let o: MyInterface

  • 断言它是一个内联字典:

    let o = { dic: <{ [name: string]: number }> { 'a': 1, 'b': 2 } }

  • 确保它是 TypeScript 为您推断的初始类型:

    foo({ dic: { 'a': 1, 'b': 2 } })

如果有一种情况,TypeScript 认为它是一个只有两个属性的普通对象,然后你稍后尝试将它用作字典,它会很不高兴。

【讨论】:

  • 很好,抱歉,这应该是索引语法 (o.dic['x'] = 1)。示例已更新。
  • 更新!如果有效,TypeScript 2 现在应该会自动为您执行此转换:github.com/Microsoft/TypeScript/wiki/…
  • 这太愚蠢了,不敢相信微软搞砸了,如果 X 是 Y 的子集,那么将 X 转换为 Y 应该很容易,没有麻烦。现在 TS 不能转换 [key: string]: string | number 但它可以转换 [key: string]: any 尽管接口只有字符串和数字
  • 扩展运算符对我有帮助 - 我不需要修改对象,所以 bar({ ...hash }) 适合我。
  • 截至今天,它似乎可以工作,只要您使用类型而不是接口。请参阅@Nikolay 的回答。
【解决方案2】:

TS 希望我们定义索引的类型。例如,告诉编译器您可以使用 any string 为对象编制索引,例如myObj['anyString'],更改:

interface myInterface {
  myVal: string;
}

到:

interface myInterface {
  [key: string]: string;
  myVal: string;
}

您现在可以在任何字符串索引上存储任何字符串值:

x['myVal'] = 'hello world'
x['any other string'] = 'any other string'

【讨论】:

  • 如果你不想失去myVal打字+自动完成功能,这就是你要走的路
  • 但是当你这样做时你会失去类型安全。 myInterface 原本应该拥有 myVal 属性,现在它可以拥有任何属性。
  • 是的,这个问题是由一个希望能够在任意索引上存储一些值的人提出的。为了清楚起见,我进行了编辑。
【解决方案3】:

对我来说,错误是通过使用类型而不是接口解决的

当函数foo具有类型而不是用于键入参数的接口时,可能会发生此错误

type MyType {
   dic: { [name: string]: number }
} 

function foo(a: MyType) {}

但是传递的值是用接口输入的

interface MyInterface {
    dic: { [name: string]: number }
}

const o: MyInterface = {
    dic: {
        'a': 3,
        'b': 5
    }
}

foo(o) // type error here

我刚用过

const o: MyType = {
    dic: {
        'a': 3,
        'b': 5
    }
}

foo(o) // it works

【讨论】:

  • 你能解释一下你得到了什么类型错误以及为什么使用类型而不是接口应该有帮助吗?我无法重现您声称的内容:Stackblitz example 按预期工作。也许您的答案已经过时(我使用的是 typescript 4.3.5)
【解决方案4】:

你可以通过foo({...o})来解决这个问题 playground

【讨论】:

    【解决方案5】:

    这是我的两分钱:

    type Copy<T> = { [K in keyof T]: T[K] }
    
    genericFunc<SomeType>() // no index signature
    
    genericFunc<Copy<SomeType>>() // no error
    

    【讨论】:

      【解决方案6】:

      就我而言,只需要使用type 而不是interface

      【讨论】:

      • 我也一样,但这是为什么呢?
      • 也为我工作。不知道为什么。
      【解决方案7】:

      此错误合法。你应该写这个来将类型标记为不可变:

      interface MyInterface {
          dic: { [name: string]: number }
      }
      
      function foo(a: MyInterface) {
          ...
      }
      
      const o = Object.freeze({
          dic: {
              'a': 3,
              'b': 5
          }
      })
      

      为什么?

      TS 编译器不能假定 o 在它被初始化和foo(o) 被调用之间不会改变。

      也许在你的代码中某处写了如下的 sn-p:

      delete o.dic.a;
      

      这就是内联版本有效的原因。在这种情况下,无法进行更新。

      【讨论】:

        猜你喜欢
        • 2019-04-28
        • 2018-12-15
        • 2021-03-06
        • 1970-01-01
        • 1970-01-01
        • 2021-05-20
        • 2022-08-21
        • 2019-05-23
        • 2021-12-07
        相关资源
        最近更新 更多