打字稿:类型中缺少索引签名

Man*_*anu 28 casting typescript

我希望MyInterface.dic像字典一样name: value,我将其定义如下:

interface MyInterface {
    dic: { [name: string]: number }
}
Run Code Online (Sandbox Code Playgroud)

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

function foo(a: MyInterface) {
    ...
}
Run Code Online (Sandbox Code Playgroud)

并输入:

let o = {
    dic: {
        'a': 3,
        'b': 5
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

我知道有一个可能的演员:let o: MyInterface = { ... }哪个可以做,但问题是,为什么打字稿不能识别我的类型?


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

foo({ 
  dic: {
    'a': 3, 
    'b': 5
  }
})
Run Code Online (Sandbox Code Playgroud)

Jöc*_*ker 40

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

  • 我也一样,但是这是为什么呢? (7认同)
  • 请参阅 https://github.com/microsoft/TypeScript/issues/15300。根据设计,接口和类型在索引签名方面的行为有所不同。 (6认同)

Tim*_*rry 32

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

{ dic: { a: number, b: number } }
Run Code Online (Sandbox Code Playgroud)

那不一样{ 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认为它只是一个只有两个属性的普通对象,那么你以后会尝试将它用作字典,那就太不开心了.

  • 扩展运算符对我的情况有帮助 - 我不需要修改对象,所以 `bar({ ...hash })` 对我有用。 (13认同)
  • 更新!TypeScript 2现在应该自动为您执行此转换,如果它有效:https://github.com/Microsoft/TypeScript/wiki/What%27s-new-in-TypeScript#implicit-index-signatures (2认同)
  • 这真是太愚蠢了,不能相信微软会这样,如果X是Y的子集,那么就很容易将X转换为Y而不用麻烦。目前,TS无法投射`[key:string]:string | 数字,但是它可以强制转换[[key:string]:any`,尽管接口仅包含字符串和数字 (2认同)
  • 截至今天,只要您使用类型而不是接口,它似乎就可以工作。请参阅@Nikolay 的回答。 (2认同)

C. *_*wis 20

TS 要我们定义索引的类型。例如,告诉编译器您可以使用任何字符串索引对象,例如myObj['anyString'],更改:

interface myInterface {
  myVal: string;
}
Run Code Online (Sandbox Code Playgroud)

到:

interface myInterface {
  [key: string]: string;
  myVal: string;
}
Run Code Online (Sandbox Code Playgroud)

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

x['myVal'] = 'hello world'
x['any other string'] = 'any other string'
Run Code Online (Sandbox Code Playgroud)

  • 但是当你这样做时你会失去类型安全。`myInterface` 原本只具有 `myVal` 属性,现在它可以具有任何属性。 (5认同)

Tim*_*aas 20

这是我的两分钱:

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

genericFunc<SomeType>() // No index signature

genericFunc<Copy<SomeType>>() // No error
Run Code Online (Sandbox Code Playgroud)


Nik*_*nyy 12

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

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

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

function foo(a: MyType) {}
Run Code Online (Sandbox Code Playgroud)

但是通过像这样的接口输入的值

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

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

foo(o) // type error here
Run Code Online (Sandbox Code Playgroud)

我刚用

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

foo(o) // it works
Run Code Online (Sandbox Code Playgroud)

  • 您能解释一下您遇到的类型错误以及为什么使用接口上的类型会有所帮助吗?我无法重现您所声称的内容:这个[Stackblitz示例](https://www.typescriptlang.org/play?ts=4.3.5#code/C4TwDgpgBAsiAq5oF4oG8CwAoKuAmAlgMYBc6UA2gHYCGAthGQM7ABOBVA5gLplUCudAEYRWUAL7ZxUbNgBm-KkWAEA9lShzVqgBQ0ycRJACU6 SVmwdgouTSLQ4ASSrXWt++my58xMmkq0DMxsHDx8giJi5ubYROosUKoGIM6u7iieON6EpJne+VAA5DSFZADMADReBbiFQqVQAKzVuNGyWFq6qqYA9D1QoJBQoqyqYgAWohDYQA) 按预期工作。也许你的答案已经过时了(我正在使用 typescript 4.3.5) (4认同)

Web*_*her 12

这个问题比OP的问题更广泛。

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

interface IObj {
  prop: string;
}

const obj: IObj = { prop: 'string' };
Run Code Online (Sandbox Code Playgroud)

我们可以分配obj给 typeRecord<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; 
Run Code Online (Sandbox Code Playgroud)

为什么会发生这种情况?为了描述它,让我们刷新一下我们对“向上转型”和“向下转型”术语的理解,以及 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)
Run Code Online (Sandbox Code Playgroud)

IObj仅需要一个道具,因此分配是正确的。

对于类型也是如此。演示

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)
Run Code Online (Sandbox Code Playgroud)

由于“向上转换”,最后两个示例可以正常工作而不会出现错误。这意味着我们将值类型转换为“上层”类型,即可以作为祖先的实体类型。换句话说,我们可以将 Dog 分配给 Animal,但不能将 Animal 分配给 Dog(参见 SOLID 原则中“L”字母的含义)。将狗分配给动物是“向上转型”,这是安全操作。

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

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

这就是为什么当您分配接口时IObjRecord<string, string>出现错误。您将其分配给扩展的类型IObjRecord<string, string>类型可以是 的后代IObj

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

例子:

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
  // UPD:
  // FYI: TS has a flag `noUncheckedIndexedAccess` which changes this behavior so every prop becomes optional
  record.value3; 
}

fn(obj); // No error here but it has to be here because of downcasting
Run Code Online (Sandbox Code Playgroud)

最后一个例子是类型和接口的比较。

聚苯乙烯

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


emm*_*bee 5

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


Ale*_*nic 5

这个错误是有效的。您应该编写类似以下选项的内容:

const o = Object.freeze({dic: {'a': 3, 'b': 5}})
const o = {dic: {'a': 3, 'b': 5}} as const
const o: MyInterface = {dic: {'a': 3, 'b': 5}}
Run Code Online (Sandbox Code Playgroud)

为什么?

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

也许在你的代码中的某个地方写了类似下面的代码片段:

delete o.dic
Run Code Online (Sandbox Code Playgroud)

这就是内联版本起作用的原因。在这种情况下,没有任何可能的更新。