弄清楚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。变量uhOh 与ISupplier 一样有效,就像您的o 一样。并且所有编译器都记得o 和uhOh 是它们是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