CorayThan
CorayThan

Reputation: 17825

Using generics in es6 Map with Typescript

I've got a project in Typescript 2.0 and I'm using es6 as my module, with the lib es2017 included so I can use maps. I've got everything working except I seem to need to do some unnecessary casts.

static readonly gameStateMap: Map<GameState, Phaser.State> = new Map([
    [GameState.PONG, new PongState() as Phaser.State],
    [GameState.BREAKOUT, new BreakoutState() as Phaser.State]
]);

Why do I need to cast them to Phaser.State? Since they both directly extend Phaser.State I thought I should be able to just stick them in the map with no problem.

I thought maybe I just needed to refine my generics declaration, so I tried:

Map<GameState, V extends Phaser.State>.

I see that syntax used in Typescript's generics docs.

But it doesn't work. Is this an ES6 generic and that's my problem? How can I fix this to not need to cast?

Also, this issue only occurs if the classes in question have different properties. The error is:

error TS2345: Argument of type '((GameState | PongState)[] | (GameState | BreakoutState)[])[]' is not assignable to parameter of type 'Iterable<[GameState, State]>'.

Upvotes: 8

Views: 24495

Answers (2)

unional
unional

Reputation: 15589

There is something wrong if your system. You need to provide more info.

It is working in the playground

enum GameState {
    PONG,
    BREAKOUT
}

namespace Phaser {
    export interface State { }
}

class PongState implements Phaser.State { }
class BreakoutState implements Phaser.State { }


let gameStateMap: Map<GameState, Phaser.State> = new Map([
    [GameState.PONG, new PongState()],
    [GameState.BREAKOUT, new BreakoutState()]
]);

UPDATE: The issue you face is that you declare type manually and rely on inferring. The inferring can only do so much. In this case, the two inner array can't be inferred to a common type, thus they ended up as [{}, {}]. The solution is to use the generic directly:

enum GameState {
    PONG,
    BREAKOUT
}

namespace Phaser {
    export interface State { }
}

class PongState implements Phaser.State { a: string }
class BreakoutState implements Phaser.State { b: string }


let gameStateMap = new Map<GameState, Phaser.State>([
    [GameState.PONG, new PongState()],
    [GameState.BREAKOUT, new BreakoutState()]
]);

Upvotes: 4

artem
artem

Reputation: 51629

When you write it in this way

let gameStateMap: Map<GameState, Phaser.State> = new Map(...)

compiler first tries to infer a type for new Map, then checks if it's compatible with the type declared for gameStateMap.

In your case, when arrays with elements having different types are involved, compiler fails to infer common type for them and uses union type instead (the intended type for Map argument apparently is [GameState, Phaser.State][] ).

Compiler needs a little help with this, you can provide it by giving explicit generic arguments to Map constructor, and then you can omit explicit type declaration for gameStateMap:

enum GameState { PONG, BREAKOUT };
namespace Phaser {
    export interface State { }
}

class PongState implements Phaser.State { a: string }
class BreakoutState implements Phaser.State { b: string}

let gameStateMap = new Map< GameState, Phaser.State > ([
    [GameState.PONG, new PongState()],
    [GameState.BREAKOUT, new BreakoutState()]
]);

Upvotes: 1

Related Questions