【问题标题】:How to declare a typed object with arbitrary string keys - Index signature is missing?如何使用任意字符串键声明类型化对象 - 缺少索引签名?
【发布时间】:2019-07-26 23:34:25
【问题描述】:

我需要声明一个具有numberstring 类型的任意字符串键和值的对象,即类似于this question

所以我写了

interface AnyObjectWithNumberAndStringValues {
  [s: string]: number | string;
}

还有一个测试函数

function f(x: AnyObjectWithNumberAndStringValues) {
  console.log(x);
}

它有点工作。我可以打电话

f({ id: 123, name: "noname" })

但是现在,我有一个接口和一个类似的对象

interface ISupplier {
  id: number;
  name: string;
}

const o: ISupplier = { id: 123, name: "noname" };

调用f(o) 会产生以下错误:

“ISupplier”类型的参数不能分配给“AnyObjectWithNumberAndStringValues”类型的参数。 'ISupplier'.ts(2345) 类型中缺少索引签名

  • 谁能解释一下原因?
  • 我该如何解决这个问题?

更准确地说:我如何声明 f 接受任何对象,只要它的值只是数字和字符串?

有关完整示例,请参阅此sandbox。请注意,它似乎是不确定的:错误可能出现也可能不出现!

【问题讨论】:

标签: typescript typescript-typings


【解决方案1】:

弄清楚indexable type 可以分配哪些类型的类型绝对是令人困惑的。 原因

f({ id: 123, name: "noname" }); // okay

有效是因为参数是一个对象字面量,它被赋予implicit index signature。发生这种情况时,编译器知道参数是一个对象字面量,因此它没有任何未知属性。因此,将其视为可分配给AnyObjectWithNumberAndStringValues 的东西是安全的。

原因

const o: ISupplier = { id: 123, name: "noname" };
f(o); // error

不起作用是因为o 被赋予了ISupplier 的类型注释。在调用f(o) 时,编译器已经忘记了o 中的具体属性。编译器知道那里可能存在未知的非string 和非number 属性。

考虑以下代码:

interface ISupriser extends ISupplier {
  surprise: boolean;
}
const s: ISupriser = { id: 123, name: "noname", surprise: true };
const uhOh: ISupplier = s;
f(uhOh); // error makes sense now

ISurpriser 包含boolean 属性,因此您不会想用它调用f()。但是每个ISupriser 也是一个ISupplier。变量uhOhISupplier 一样有效,就像您的o 一样。并且所有编译器都记得ouhOh 是它们是ISupplier 的实例。这就是f(o) 失败的原因……这是为了防止你不小心调用f(uhOh)


请注意,您执行的任何解决方法允许您在不是对象文字的内容上调用 f() 或类似函数都有可能允许意外参数,以及编译器不知道的不良属性(或不记得了)。但无论如何,让我们看看这些解决方法:

一直是noted,一种解决方法是使用type alias 代替ISupplier,而不是接口。显然,这里的权衡是扩展类型别名并不那么容易(您可以通过交集获得相同的效果),因此将其视为具有隐式索引签名是“足够安全的”:

type TSupplier = {
  id: number;
  name: string;
};
const p: TSupplier = { id: 123, name: "noname" };
f(p); // okay

添加不需要的属性可能不像使用接口那样“容易”,但它仍然很容易:

const ohNo: TSupplier = uhOh; // okay
f(ohNo); // no error, but there probably should be one!

另一种解决方法,尤其是在您无法更改 ISupplier 的情况下,是在您的函数中使用受约束的泛型类型而不是索引签名,如下所示:

function g<T extends Record<keyof T, number | string>>(x: T) {
  console.log(x);
}

g() 函数基本上只接受已知属性可分配给number | string 的参数:

g({ id: 123, name: "noname" }); // okay
g(o); // okay
g(s); // error, "surprise" is incompatible

同样,如果编译器不知道某个属性,它就无法检查它,所以这些也没有错误:

g(uhOh); // no error, but there probably should be one
g(ohNo); // no error, but there probably should be one

无论如何,希望对你有所帮助。祝你好运!

Link to code

【讨论】:

  • 太棒了。我被“'ISupplier”类型中缺少索引签名所误导,这听起来像是其中缺少某些东西,但缺少的是any ISupplier具有的约束没有错误输入的值。使用您的ISurpriser,它非常干净。
猜你喜欢
  • 2019-11-13
  • 2019-04-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-06-27
  • 1970-01-01
  • 2022-08-21
相关资源
最近更新 更多