Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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]>'.
like image 846
CorayThan Avatar asked Jul 20 '17 19:07

CorayThan


People also ask

Does TypeScript support generics?

TypeScript fully supports generics as a way to introduce type-safety into components that accept arguments and return values whose type will be indeterminate until they are consumed later in your code.

How do generics work in TypeScript?

Generics allow creating 'type variables' which can be used to create classes, functions & type aliases that don't need to explicitly define the types that they use. Generics makes it easier to write reusable code.

Does TypeScript support maps?

The Map is a new data structure introduced in ES6 so it is available to JavaScript as well as TypeScript. A Map allows storing key-value pairs (i.e. entries), similar to the maps in other programming languages e.g. Java HashMap.


2 Answers

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()]
]);
like image 121
unional Avatar answered Oct 20 '22 00:10

unional


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()]
]);
like image 32
artem Avatar answered Oct 20 '22 02:10

artem