Typescript: how to return the proper class and gen

2019-07-14 11:23发布

I'm trying to find a way to inject an object into another object (dependency injection), in which the container object can return the proper injected object type, and no need to explicitly pass types to the function (type inference).
To make sure the injected objects have a common interface and a default behavior, they'd be instances of an Abstract class.

Given that I'd need such a behavior at some ponctual place, I would like to avoid using a dependency injection framework to do so.

It seems to me that what I need is to set the generic type on object instantiation because generic parameters cannot be redefined afterwards. Consequently I'd need to use a factory (new() interface)


Here is an example. Test it in Typescript Playground. To make things simple, I'm looking for createMyClass2 to return an object typed ArrayClass<string>, without specifying the type explicitly when calling it (type inference).
It may be considered that ArrayClass and ArrayClassAbstract are some kind of plugins defined in different NPM modules for example.

class ArrayClassAbstract<MYTYPE> {
    public arr: Array<MYTYPE> = []
    constructor(element: MYTYPE) {
        this.arr.push(element);
    }
}

class ArrayClass<MYTYPE> extends ArrayClassAbstract<MYTYPE> {
    test() { 
        return this.arr;
    }
}

class SomeOtherArrayClass<MYTYPE> extends ArrayClassAbstract<MYTYPE> {
    test() { 
        return this.arr + "something different";
    }
}

function createMyClass<MYTYPE>
    (className: { new <MYTYPE>(element: MYTYPE): ArrayClassAbstract<MYTYPE> }, element: MYTYPE) { 
    return new className<MYTYPE>(element);
}

let a = createMyClass(ArrayClass, "hop!");   // type: {ArrayClassAbstract<string>}
a.test();                                    // Doesn't work... "test" doesn't exist in ArrayClassAbstract

Good generic type, wrong class type...

function createMyClass2<T extends ArrayClassAbstract<string>>
    (className: { new (element: string): T }, element: string) { 
    return new className(element);
}

let b = createMyClass2(ArrayClass, "hop!");  // type: {ArrayClass<any>}
b.test();                                    // Works but array elements typed as "any"

Good class type, wrong generic type. I don't know if there's something wrong in my way to declare the factory, or if I'm victim of Typescript limitations...

Any idea?

2条回答
在下西门庆
2楼-- · 2019-07-14 11:41

How about using overloads?

function createMyClass2<MYTYPE>(type: typeof ArrayClass, element: MYTYPE): ArrayClass<MYTYPE>;
function createMyClass2<MYTYPE>(type: typeof SomeOtherArrayClass, element: MYTYPE): SomeOtherArrayClass<MYTYPE>;
function createMyClass2(className, element) {
    return new className(element);
}

let b = createMyClass2(ArrayClass, "hop!");  // type: {ArrayClass<string>}
b.test();
let c = createMyClass2(ArrayClass, 123);  // type: {ArrayClass<number>}
c.test();
let d = createMyClass2(SomeOtherArrayClass, "hop!");  // type: {SomeOtherArrayClass<string>}
d.test();
查看更多
▲ chillily
3楼-- · 2019-07-14 11:53

You just need a second type parameter. It can still be inferred:

// Renamed some stuff for clarity
type ConstructorFor<CtorArg, Inst> = { new(element: CtorArg): Inst; }; 
function createMyClass<CtorArg, InstType extends ArrayClassAbstract<CtorArg>>(ctor: ConstructorFor<CtorArg, InstType>, arg: CtorArg) { 
    return new ctor(arg);
}

let a = createMyClass(ArrayClass, "hop!");   // type: {ArrayClass<string>}
a.test(); // OK
查看更多
登录 后发表回答