简体   繁体   中英

How can I control the return type based on an argument in Typescript?

I have a factory class which will return different classes which all implement a same interface but with different generic. However, my factory class is unable to determine the different generic type used by the interfaces.

For example, I have the following interfaces:

interface ICityData<T> {
    data: T
}

interface LondonData {
    a: string,
    b: number
}

interface BerlinData {
    c: string,
    d: string
}

interface NewYorkData {
    a: Record<any, any>,
    e: number
}

interface ICityConcrete<T> {
    retrieve(): T
}

I have the follow concrete classes which all implement the ICityConcrete interface but uses a different generic type:

class LondonConcrete implements ICityConcrete<LondonData> {
    retrieve(): LondonData {
        return {
            a: 'test',
            b: 123
        }
    }
}

class BerlinConcrete implements ICityConcrete<BerlinData> {
    retrieve(): BerlinData {
        return {
            c: 'abc',
            d: 'efg'
        }
    }
}

class NewYorkConcrete implements ICityConcrete<NewYorkData> {
    retrieve(): NewYorkData {
        return {
            a: {
                name: 'test'
            },
            e: 321
        }
    }
}

Finally, I have a factory to create those classes depending on the city provided as the argument:

enum City {
    London,
    NewYork,
    Berlin
}

class MyFactory {
    public static create(city: City): ICityConcrete<TDependsOnCity> {     // TDependsOnCity could be LondonData, BerlinData or NewYorkData depending on the city argument
        switch (city) {
            case City.London:
                return new LondonConcrete();
            case City.Berlin:
                return new BerlinConcrete();
            default:
                return new NewYorkConcrete(); 
        }
    }
}

const myCreatedInstance = MyFactory.create(City.London);

Here's the problem, I want myCreatedInstance to be of type ICityConcrete<TDependsOnCity> . In other words, the type should depend on the city provided. If City.London was provided in the argument, myCreatedInstance should be of type ICityConcrete<LondonData> .

I tried to rearrange things in different ways and most of them have ICityConcrete<any> no matter what I use as city .

在此处输入图像描述

How can I control the return type based on the argument provided in this case?

TS Playground

You could define and use a city type to constructor map:

const cityConstructors = {
    [City.London]: LondonConcrete,
    [City.Berlin]: BerlinConcrete,
    [City.NewYork]: NewYorkConcrete,
};

class MyFactory {
    public static create<TCity extends City>(city: TCity): InstanceType<(typeof cityConstructors)[TCity]>
    public static create(city: City) {
        return new cityConstructors[city]();
    }
}

const myCreatedInstance = MyFactory.create(City.London) // LondonConcrete

Playground

Try to use overloads :

enum City {
    London,
    NewYork,
    Berlin
}

interface ICityData<T> {
    data: T
}

interface LondonData {
    a: string,
    b: number
}

interface BerlinData {
    c: string,
    d: string
}

interface NewYorkData {
    a: Record<any, any>,
    e: number
}

interface ICityConcrete<T> {
    retrieve(): T
}

class LondonConcrete implements ICityConcrete<LondonData> {
    retrieve(): LondonData {
        return {
            a: 'test',
            b: 123
        }
    }
}

class BerlinConcrete implements ICityConcrete<BerlinData> {
    retrieve(): BerlinData {
        return {
            c: 'abc',
            d: 'efg'
        }
    }
}

class NewYorkConcrete implements ICityConcrete<NewYorkData> {
    retrieve(): NewYorkData {
        return {
            a: {
                name: 'test'
            },
            e: 321
        }
    }
}

class MyFactory {
    public static create(city: City.Berlin): ICityConcrete<BerlinData>
    public static create(city: City.London): ICityConcrete<LondonData>
    public static create(city: City): ICityConcrete<NewYorkData>
    public static create(city: City) { 
        switch (city) {
            case City.London:
                return new LondonConcrete();
            case City.Berlin:
                return new BerlinConcrete();
            default:
                return new NewYorkConcrete(); 
        }
    }
}

const myCreatedInstance = MyFactory.create(City.London) //  ICityConcrete<LondonData>
const myCreatedInstance2 = MyFactory.create(City.Berlin) //  ICityConcrete<BerlinData>
const myCreatedInstance3 = MyFactory.create(City.NewYork) //  ICityConcrete<NewYorkData>

Playground

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