如何声明带有任意字符串键的类型化对象-缺少索引签名?

时间:2019-07-26 23:34:25

标签: typescript typescript-typings

我需要声明一个具有任意字符串键和类型为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。请注意,这似乎是不确定的:错误可能会或可能不会出现!

1 个答案:

答案 0 :(得分:1)

弄清楚可以分配给indexable type和来自implicit index signature的类型是绝对令人困惑的。

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

之所以有用,是因为该参数是对象文字,它被赋予noted。发生这种情况时,编译器知道该参数是对象文字,因此它没有任何未知的属性。因此,将其视为可分配给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()或类似的函数,这有可能允许意外的参数,而编译器不知道这些属性具有错误的属性。 (或不记得了)。但是还是让我们看看这些变通方法:

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

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

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

{{3}}