我正在尝试创建一个对象,该对象是多个项目键之间的关系,并且项目键具有不同的版本,它们具有自己的数据类型。
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 question 和 this open bug 的骗局吗?
(这些似乎与数组/元组类型有关,而我的是对象/映射)。
如果是这样,在我的情况下推荐的解决方案是什么?
如果不是,这里错误的原因是什么?
答案 0 :(得分:2)
只是想检查一下 - 这是一个 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
Typescript 必须支持一般情况并检查作为参数传递的所有可能值。 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
答案 1 :(得分:0)
我建议在 TypeScript 存储库上提交一个新问题。如果它是重复的,他们就会关闭它。至于推荐的解决方案,稍微调整泛型类型约束似乎可以解决问题:Playground
答案 2 :(得分:0)
即使我无法为您提供问题的实际答案,我也可以为您提供替代解决方案。
我认为(我说的是“我认为”,请不要将其视为事实)问题在于,在编译时 TypeScript 无法知道您将作为 itemKey
参数传递什么,所以它不知道在运行时您是否会传递 itemKey
、version
对,而这对 myMap
对象中不存在...
曾经说过,我的解决方案建议:
function getDataForTypeAndVersion<M extends MyMapObject, T extends keyof M, U extends keyof M[T]> (map: M, itemKey: T, version: U) : ItemObject<T,U> {
const versionMap = map[itemKey];
const item = versionMap[version];
return item;
}
这是一个 Playground,它(如果我理解正确的话)应该尊重您的所有要求。