简体   繁体   中英

TypeScript Generics: any interface implementer

I have a problem with generics in TypeScript.

I have an interface called Obstacle :

import Shape from "./Shape";

interface Obstacle<S extends Shape> {
    readonly type: string;
    readonly shape: S;
}

export default Obstacle;

Obstacle is a wrapper around another interface Shape :

interface Shape {
    readonly name: string;
}

export default Shape;

I can use these two interfaces, to create a class that implements Shape , for example Rectancle or Circle .

Then, I create another class, for example RectangleObstacle or CircleObstacle which implements Obstacle<Rectangle> or Obstacle<Circle> , respectively.

But, my question is, when I use them, in an array of obstacles (it should hold any type of obstacle), like this:

import Obstacle from "./Obstacle";

interface Data {
    obstacles: Obstacle<any>[]; /* What should I put here? In the generic parameters list? */
}

const DATA: Data = {
    obstacles: []
};

I have tried putting Obstacle<Shape>[] and Obstacle<any extends Shape>[] , but it doesn't work. Also, I should be able to distinguish between different types of obstacles, like so:

function somefunc(): void {
    for(let i: number = 0; i < DATA.obstacles.length; i++) {
        const o: Obstacle = DATA.obstacles[i]; /* what to write here? */
        switch(o.type) {
            case "rectangle":
                /* For example, writting this: */
                o.shape.width;
                /* results in: property width does not exist on type Shape */
                break;
            case "circle":
                /* ... */
                break;
        }
    }
}

You need to use a discriminated union

type AppObstacle = RectangleObstacle | CircleObstacle

interface Data {
    obstacles: AppObstacle[]; 
}

Playground link

Also, to apply suggestion by AlekseyL., if obstacle is only a container for shape with no additional data or logic associated with it, you may change Shape to discriminated union as follows:

interface Shape {
    readonly type: string;
    readonly name: string;
}

interface Rectangle extends Shape {
  type: 'rectangle';
  width: number;
}

interface Circle extends Shape {
  type: 'circle';
  radius: number
}

type AppShape = Rectangle | Circle;

interface Data {
    obstacles: Obstacle[]; 
}

const DATA: Data = {
    obstacles: []
};

function somefunc(): void {
    for(let i: number = 0; i < DATA.obstacles.length; i++) {
        const o = DATA.obstacles[i]; 
        const shape = o.shape;
        switch(shape.type) {
            case 'rectangle':
                shape.width;
                break;
            case 'circle':
                shape.radius;
                break;
        }
    }
}

Playground link

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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