简体   繁体   English

TypeScript 接口映射属性泛型类型

[英]TypeScript interface mapped property generic type

I'm trying to make this work我正在努力使这项工作

interface ObjectPool<Ids, T> {
  pool: {
    [K in Ids]: T<K>;
  };
};

interface Player<Id> {
  id: Id;
}

let playerPool: ObjectPool<0 | 1 | 2, Player>;

so that以便

playerPool[0].id === 0;
playerPool[1].id === 1;
playerPool[2].id === 2;
// playerPool[3] error

but typescript says I need a generic parameter at Player in let playerPool: ObjectPool<0 | 1 | 2, Player>;但是 typescript 说我需要一个通用参数在Player中的let playerPool: ObjectPool<0 | 1 | 2, Player>; let playerPool: ObjectPool<0 | 1 | 2, Player>; so I tried let playerPool: ObjectPool<0 | 1 | 2, Player<_>>;所以我试着let playerPool: ObjectPool<0 | 1 | 2, Player<_>>; let playerPool: ObjectPool<0 | 1 | 2, Player<_>>; but that doesn't work too但这也行不通

If you write T<K> , then T needs to be some particular type operation (eg, type T<K> =... or interface T<K> {... or class T<K>... ).如果您编写T<K> ,则T需要是某种特定类型的操作(例如, type T<K> =...interface T<K> {...class T<K>... )。 There's no way to write T<K> where T is a generic type parameter.没有办法编写T<K> ,其中T泛型类型参数。 That would require so-called higher-kinded types , of the sort requested in microsoft/TypeScript#1213 , and TypeScript has no direct support for that.这将需要microsoft/TypeScript#1213中要求的所谓的更高种类的类型,而 TypeScript 对此没有直接支持。

You could step back and try to think of exactly what you want to do, and if there's any way to represent it without needing higher kinded types.你可以退后一步,试着想一想你想要做什么,以及是否有任何方法可以在不需要更高种类的类型的情况下表示它。 If all you want is for ObjectPool<P, T> to have all property keys in P , and for each such key K , you want the property value to have an id property equal K in addition to some other properties specified by T , then you can separate out the id part in the definition so that T is just a regular type.如果您想要的只是ObjectPool<P, T>拥有P中的所有属性键,并且对于每个这样的键K ,除了T指定的其他一些属性之外,您希望属性值具有等于Kid属性,那么您可以在定义中分离出id部分,以便T只是一个常规类型。 For example:例如:

type ObjectPool<P extends PropertyKey, T> =
  { [K in P]: { id: K } & T };

Now you could define Player without making it generic:现在您可以定义Player而不使其通用:

interface Player {
  id: number,  // <-- you don't necessarily need this anymore
  name: string,
}

And now an ObjectPool<0 | 1 | 2, Player>现在是一个ObjectPool<0 | 1 | 2, Player> ObjectPool<0 | 1 | 2, Player> ObjectPool<0 | 1 | 2, Player> should behave as desired: ObjectPool<0 | 1 | 2, Player>的行为应符合要求:

function processPlayerPool(playerPool: ObjectPool<0 | 1 | 2, Player>) {
  playerPool[0].id === 0;
  playerPool[1].id === 1;
  playerPool[2].id === 2;
  playerPool[2].name;
  playerPool[3] // error
}

You can then define other types to use instead of Player and use them too:然后,您可以定义其他类型来代替Player并使用它们:

interface Wall {
  location: string
  orientation: string
}

function processSmallWallPool(wallPool: ObjectPool<0 | 1, Wall>) {
  wallPool[0].location // okay
  wallPool[0].id === 0; // okay
  wallPool[1].orientation // okay
  wallPool[2] // error
}

You mentioned in a comment that you have 2,000 Wall objects in the pool.您在评论中提到池中有 2,000 个Wall对象。 That's a lot of elements to put in a union , but sure, you could do it (code generation is going to be easier than trying to convince the compiler to compute it):这是要放入union的很多元素,但可以肯定的是,您可以做到(代码生成将比试图说服编译器计算它更容易):

// console.log("type WallIds = " + Array.from({ length: 2000 }, (_, i) => i).join(" | "));
type WallIds = 0 | 1 | 2 | 3 | 4 | // ✂ SNIP! 
  | 1995 | 1996 | 1997 | 1998 | 1999

And then ObjectPool<WallIds, Wall> will also behave as desired:然后ObjectPool<WallIds, Wall>也将按需要运行:

function processWallPool(wallPool: ObjectPool<WallIds, Wall>) {
  wallPool[214].location
  wallPool[100].id // 100
  wallPool[1954].orientation
  wallPool[2021] // error
}

Please note though that the compiler really can't do much analysis on a union of numeric literals.请注意,尽管编译器确实无法对数字文字的联合进行太多分析。 You might have more trouble than you expected with this.您可能会遇到比您预期的更多的麻烦。 If you try to loop over the elements of wallPool with a numeric index i , the compiler will complain:如果您尝试使用数字索引i wallPool的元素,编译器会报错:

  for (let i = 0; i < 2000; i++) {
    wallPool[i] // error! 
  //~~~~~~~~~~~ 
  // No index signature with a parameter of type 'number' 
  // was found on type 'ObjectPool<WallIds, Wall>'
  }

It has no idea that i is guaranteed to be a value of type WallIds in that loop.它不知道i在该循环中被保证是WallIds类型的值。 It infers number , and you can't index into wallPool with any old number .它推断number ,并且您不能使用任何旧number索引到wallPool It needs to be a value of the WallIds union.它必须是WallIds联合的值。 You could assert that i is a WallIds :您可以断言iWallIds

  for (let i = 0 as WallIds; i < 20000; i++) {
    wallPool[i] // no error, but, 20000 is an oopsie
  }

but, as shown above, you run into the problem that the compiler can't understand that i++ might make i no longer a valid WallIds , as explained in this comment of microsoft/TypeScript#14745 .但是,如上所示,您会遇到编译器无法理解i++可能使i不再是有效WallIds的问题,如microsoft/TypeScript#14745此评论中所述。

If you're only ever going to be indexing into WallPool with a numeric literal value, like wallPool[123] or wallPool[1987] , then that's fine.如果您只打算使用数字文字值(如wallPool[123]wallPool[1987] )对WallPool进行索引,那很好。 But as soon as you start storing and manipulating indices of type number , you will likely hit a roadblock with this approach.但是,一旦您开始存储和操作number类型的索引,您可能会遇到这种方法的障碍。 It might still be worth it to you, but it's important to be aware of it.它对您来说可能仍然值得,但重要的是要意识到这一点。

Playground link to code Playground 代码链接

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM