typescript - 嵌套索引类型导致“不能用于索引类型”错误
问题描述
我正在尝试创建一个对象,该对象是多个项目键之间的关系,并且项目键具有不同的版本,这些版本具有自己的数据类型。
const myMap : MyMapObject = {
"foo": {
"1": {
type: "foo",
version: "1",
data: {
name: "bob"
}
},
"2" : {
type: "foo",
version: "2",
data: {
firstName: "Bob",
lastName: "Jones"
}
}
},
"bar" : {
"1": {
type: "bar",
version: "1",
data: {
age: 1
}
}
}
}
这是我的打字稿:
type ItemTypes = "foo" | "bar";
type Version = string;
type Foo = {
"1": {
name: string;
};
"2": {
firstName: string;
lastName: string;
}
}
type Bar = {
"1": {
age: number;
}
}
type BaseTypeMap = {
"foo": Foo;
"bar": Bar;
}
type VersionMap<T extends ItemTypes> = BaseTypeMap[T];
type ItemObject<T extends ItemTypes, U extends keyof VersionMap<T>> = {
type: T;
version: U;
data: VersionMap<T>[U];
}
type MyMapObject = {
[K in ItemTypes] : {
[J in keyof VersionMap<K>] : ItemObject<K, J>;
}
}
function getDataForTypeAndVersion<T extends ItemTypes, U extends keyof VersionMap<T>> (itemKey: T, version: U) : ItemObject<T,U> {
const versionMap = myMap[itemKey] ;
const item = versionMap[version]; //Type 'U' cannot be used to index type 'MyMapObject[T]'.(2536) <-- Error here
return item;
}
//The function appears to work fine.
const foo1 = getDataForTypeAndVersion("foo", "1");
foo1.data.name;
foo1.data.firstName; //expected error
const foo2 = getDataForTypeAndVersion("foo", "2");
const foo3 = getDataForTypeAndVersion("foo", "3"); //expected error
const bar1 = getDataForTypeAndVersion("bar", "1");
const char1 = getDataForTypeAndVersion("chaz", "1"); //expected error
只是想检查一下 - 这是这个Stack Overflow 问题和这个开放错误的欺骗吗?
(这些似乎与数组/元组类型有关,而我的是对象/映射)。
如果是这样,在我的情况下推荐的解决方案是什么?
如果不是,这里错误的原因是什么?
解决方案
只是想检查一下 - 这是这个 Stack Overflow 问题和这个开放错误的欺骗吗?
您遇到的问题不是重复的。对元组和数组进行操作会keyof
报告数组的所有键(例如length
, forEach
),而不仅仅是元组/数组的索引。你的问题有点微妙。
您的问题非常不直观,主要源于 Typescript 处理文字类型的方式。感觉在您的特定情况下 TS 应该有足够的信息来推断 U 可以用来索引基础类型。但请考虑一般情况:
const fooOrBar = "foo" as ItemTypes;
const barOrFoo = getDataForTypeAndVersion(fooOrBar, "2"); // an error since TS does not know what is exact type of the first argument
打字稿必须支持一般情况并检查作为参数传递的所有可能值。最广泛的类型T extends ItemTypes
是"foo" | "bar"
。碰巧在您的情况下keyof VersionMap<ItemTypes>
是"1"
,但在最通用的情况下,此类型可能为空(又名never
),因此无法用于索引任何其他类型。
TS 将来有可能通过更好的推理引擎来支持您的用例。但它本身绝对不是一个错误 - TS 在这里只是采取更安全的赌注。
下面,我在尽量接近初衷的同时,提出一个可能的解决方案。该解决方案基本上将参数作为一[type, version]
对纠缠在一起,并用条件类型证明该对可用于索引嵌套结构。我觉得它可以进一步简化一点。实际上,我的首选方法是从表示最嵌套结构的值开始并从中创建类型(以 开头typeof
)并尽量不使用/引入冗余信息 - 但它有点超出原始问题的范围。
type ItemTypes = "foo" | "bar";
type Foo = {
"1": {
name: string;
};
"2": {
firstName: string;
lastName: string;
}
}
type Bar = {
"1": {
age: number;
}
}
type BaseTypeMap = {
"foo": Foo;
"bar": Bar;
}
type VersionMap<T extends ItemTypes> = BaseTypeMap[T];
type ItemObject<A extends MyMapObjectParams> = {
type: A[0];
version: A[1];
// data: BaseTypeMap[A[0]][A[1]]; // the same TS2536 error
data: A[1] extends keyof BaseTypeMap[A[0]] ? BaseTypeMap[A[0]][A[1]] : never; // a way to make TS happy by proving we are able to index the underlying type
}
type MyMapObject = {
[K in ItemTypes] : {
[J in keyof VersionMap<K>] : [K, J] extends MyMapObjectParams ? ItemObject<[K, J]> : never;
}
}
type MyMapObjectParams = {
[K in ItemTypes] : {
[J in keyof VersionMap<K>] : [type: K, version: J]
}[keyof VersionMap<K>]
}[ItemTypes]
const myMap : MyMapObject = {
"foo": {
"1": {
type: "foo",
version: "1",
data: {
name: "bob"
}
},
"2" : {
type: "foo",
version: "2",
data: {
firstName: "Bob",
lastName: "Jones"
}
}
},
"bar" : {
"1": {
type: "bar",
version: "1",
data: {
age: 1
}
}
}
}
function getDataForTypeAndVersion <A extends MyMapObjectParams>(...args: A) : ItemObject<A> {
return myMap[args[0]][args[1]]
}
const foo1 = getDataForTypeAndVersion("foo", "1");
foo1.data.name;
foo1.data.firstName; //expected error
const foo2 = getDataForTypeAndVersion("foo", "2");
const foo3 = getDataForTypeAndVersion("foo", "3"); //expected error
const bar1 = getDataForTypeAndVersion("bar", "1");
const bar2 = getDataForTypeAndVersion("bar", "2"); // expected error
const char1 = getDataForTypeAndVersion("chaz", "1"); //expected error
推荐阅读
- batch-file - 有没有办法将windows服务的pid保存在var中?
- google-app-engine - Vue 生产 URL 重定向在 gae 上返回 404
- java - 这是 Netty 从封闭通道读取的示例吗?
- python - Pandas:转置按列 a 分组的列 (b,c,d) 作为索引
- r - 如何以长格式改变小标题的值
- kotlin - 我可以在没有运行的情况下在 Kotlin 中创建一个可变的函数集合吗?
- javascript - 如何使用“spec”和“mocha-multi-reporters”将失败的测试结果输出到控制台
- c - 单引号和撇号的区别?
- sql - 如何避免 SQL 中的歧义列错误?
- python - 如何使用 itertools 解决这个问题?