类型中缺少索引签名

我希望 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 = { ... }做的把戏,但 为什么是字体脚本不识别我的类型?


附加: 如果 o声明为内联,则可以正常工作:

foo({
dic: {
'a': 3,
'b': 5
}
})
127617 次浏览

问题是,当推断出类型时,那么 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 认为它是一个只有两个属性的普通对象,然后您以后试图将它用作字典,那么它将不满意。

TS 希望我们定义索引的类型。例如,要告诉编译器可以使用 任何线(例如 myObj['anyString'])索引对象,请更改:

interface MyInterface {
myVal: string;
}

致:

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

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

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

游乐场连结。

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

如果函数 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

你可以通过做 foo({...o})来解决这个问题 游乐场

这个错误是 合法的。您应该改写下面的代码,将类型标记为不可变的:

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


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


const o = Object.freeze({
dic: {
'a': 3,
'b': 5
}
})

为什么?

TypeScript 编译器不能假定在初始化 o和调用 foo(o)之间 o不会发生变化。

也许在您的代码中的某个地方写了下面这样的代码片段:

delete o.dic.a;

这就是为什么内联版本可以工作。在这种情况下,没有任何可能的更新。

我的建议是:

type Copy<T> = { [K in keyof T]: T[K] }


genericFunc<SomeType>() // No index signature


genericFunc<Copy<SomeType>>() // No error

在我的例子中,只需要使用 type而不是 interface

这似乎是搜索关键词的最高结果。如果您已经有了一个索引类型(string,string) ,但是您不能更改该输入的类型,那么您还可以这样做:

foo({...o}) // Magic

对于你的问题,另一种方法是:

interface MyInterface {
[name: string]: number
}


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


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


foo(o);

下面这个简单的技巧可能也很有用:

type ConvertInterfaceToDict<T> = {
[K in keyof T]: T[K];
};

这种转变帮助我解决了这个问题:

类型的参数不能赋给 键入‘ Record < string,string >

我无法直接修改它,因为它来自第三方包。

这个问题比 OP 的问题要宽一些。

例如,让我们定义一个接口和该接口的变量

interface IObj {
prop: string;
}


const obj: IObj = { prop: 'string' };

我们可以把 obj分配给 Record<string, string>类型吗?

答案是 没有

// TS2322: Type 'IObj' is not assignable to type 'Record<string, string>'. Index signature for type 'string' is missing in type 'IObj'.
const record: Record<string, string> = obj;

为什么会这样? 为了描述它,让我们更新我们对“ 向上倾斜”和“ 情绪低落”术语的理解,以及在 SOLID 原则中“ L”字母的含义是什么。

下面的示例可以正常工作,因为我们将“宽”类型分配给更严格的类型。

演示

const initialObj = {
title: 'title',
value: 42,
};


interface IObj {
title: string;
}


const obj: IObj = initialObj; // No error here


obj.title;
obj.value; // Property 'value' does not exist on type 'IObj'.(2339)

IObj只需要一个道具,所以分配是正确的。

对于 Type.演示也是如此

const initialObj = {
title: 'title',
value: 42,
};


type TObj = {
title: string;
}


const obj: TObj = initialObj; // No error here


obj.title;
obj.value; // Property 'value' does not exist on type 'TObj'.(2339)

最后两个例子没有错误,因为“向上转换”。这意味着我们将一个值类型转换为“上”类型,转换为可以作为祖先的实体类型。换句话说,我们可以把狗分配给动物,但是不能把动物分配给狗(在 SOLID 原则中是 看“ L”字母的意思)。将狗分配给动物是“向上传输”,这是安全的操作。

Record<string, string>比只有一个属性的对象宽得多。它可以有任何其他属性。

const fn = (record: Record<string, string>) => {
record.value1;
record.value2;
record.value3; // No errors here
}

这就是为什么当你把 IObj接口分配给 Record<string, string>时,你会得到一个错误。将其分配给扩展 IObj的类型。Record<string, string>类型可以是 IObj的后代。

在其他答案中,提到使用 类型可以解决这个问题。但我认为这是错误的行为,我们应该避免使用它。

例如:

type TObj = {
title: string;
}


const obj: TObj = {
title: 'title',
};




const fn = (record: Record<string, string>) => {
record.value1;
record.value2;
// No errors here because according to types any string property is correct
record.value3;
}


fn(obj); // No error here but it has to be here because of downcasting

附言。

看看这个 问题和相关的问题,和有趣的 评论