Home > Net >  TypeScript narrowing argument type in constructor
TypeScript narrowing argument type in constructor

Time:11-03

I have an entry class called BackgroundLayer. It takes two arguments, (1) the type of the background and (2) the configuration of that background. When (1) is type of Background.Grid, then the configuration needs to be of type GridBackgroundConfig. When (1) is type of Background.Checkered the configuration needs to be type of CheckeredBackgroundConfig. What I want is, when typing new BackgroundLayer(Background.Grid, the configuration (argument 2) needs to be type of GridBackgroundConfig. The source code below is my try, but it allows passing Background.Grid as (1) and an object of type CheckeredBackgroundConfig as (2) which shouldnt be allowed. I tried using multiple constructor implementations (constructor(type: Background.Grid, config: GridBackgroundConfig); constructor(type: BackgroundEnum.Checkered,config: CheckeredBackgroundConfig); but then I need to create a new constructor for every entry in Background manually. I wondered if it was possible to narrow the type of the second argument, if the type of the first argument is known.

enum Background {
    Grid = "Grid";
    Checkered = "Checkered";
}

interface LayerConfig {
    /* ... */
}

interface GridBackgroundConfig extends LayerConfig {
    /* ... */
}

interface CheckeredBackgroundConfig extends LayerConfig {
    /* ... */
}

class Layer {
    protected config: LayerConfig;

    constructor(config: LayerConfig) {
        this.config = config;
    }
}

class BackgroundLayer extends Layer {
    private background: Background;

    constructor(type: Background, config: GridBackgroundConfig | CheckeredBackgroundConfig) {
        super(config);

        if (type === Background.Grid) {
            this.background = new Grid(config as GridbackgroundConfig);
        } else if (type === Background.Checkered) {
            this.background = new Checkered(config as CheckeredBackgroundConfig);
        } else {
            throw "check argument types";
        }
    }
}

class Grid {
    private config: GridBackgroundConfig;

    constructor(config: GridBackgroundConfig) {
        this.config = config;
    }
}

class Checkered {
    private config: CheckeredBackgroundConfig;

    constructor(config: CheckeredBackgroundConfig) {
        this.config = config;
    }
}

CodePudding user response:

You should probably stick with using overloads here, but anyways, if you define a type that maps background types to their respective configs...

type ConfigMap = {
    [Background.Grid]: GridBackgroundConfig;
    [Background.Checkered]: CheckeredBackgroundConfig;
};

You should be able to make your class generic and edit the constructor to use the type parameter:

class BackgroundLayer<Type extends Background> extends Layer {
    private background: Grid | Checkered;

    constructor(type: Type, config: ConfigMap[Type]) {

Then the type of config depends on what was given to type.

Playground

  • Related