2 votes

Typescript : comment retourner la bonne classe et le bon type générique dans une dépendance

J'essaie de trouver un moyen d'injecter un objet dans un autre objet (injection de dépendance), dans lequel l'objet conteneur peut retourner le bon injecté type d'objet et pas besoin de passer explicitement les types à la fonction (inférence de type).
Pour s'assurer que les objets injectés ont une interface commune et un comportement par défaut, ils seront des instances d'une classe abstraite.

Étant donné que j'aurais besoin d'un tel comportement à un endroit ponctuel, je voudrais éviter d'utiliser un cadre d'injection de dépendances pour le faire.

Il me semble que ce dont j'ai besoin, c'est de définir le type générique lors de l'instanciation de l'objet, car les paramètres génériques ne peuvent pas être redéfinis par la suite. Par conséquent, j'aurais besoin d'utiliser un usine ( new() interface)


Voici un exemple. [Testez-le dans Typescript Playground](https://www.typescriptlang.org/play/index.html#src=class%20ArrayClassAbstract%3CMYTYPE%3E%20%7B%0D%0A%20%20%20%20public%20arr%3A%20Array%3CMYTYPE%3E%20%3D%20%5B%5D%0D%0A%20%20%20%20constructor(element%3A%20MYTYPE)%20%7B%0D%0A%20%20%20%20%20%20%20%20this.arr.push(element)%3B%0D%0A%20%20%20%20%7D%0D%0A%7D%0D%0A%0D%0Aclass%20ArrayClass%3CMYTYPE%3E%20extends%20ArrayClassAbstract%3CMYTYPE%3E%20%7B%0D%0A%20%20%20%20test()%20%7B%20%0D%0A%20%20%20%20%20%20%20%20return%20this.arr%3B%0D%0A%20%20%20%20%7D%0D%0A%7D%0D%0A%0D%0Aclass%20SomeOtherArrayClass%3CMYTYPE%3E%20extends%20ArrayClassAbstract%3CMYTYPE%3E%20%7B%0D%0A%20%20%20%20test()%20%7B%20%0D%0A%20%20%20%20%20%20%20%20return%20this.arr%20%2B%20%22something%20different%22%3B%0D%0A%20%20%20%20%7D%0D%0A%7D%0D%0A%0D%0Afunction%20createMyClass%3CMYTYPE%3E%0D%0A%20%20%20%20(className%3A%20%7B%20new%20%3CMYTYPE%3E(element%3A%20MYTYPE)%3A%20ArrayClassAbstract%3CMYTYPE%3E%20%7D%2C%20element%3A%20MYTYPE)%20%7B%20%0D%0A%20%20%20%20return%20new%20className%3CMYTYPE%3E(element)%3B%0D%0A%7D%0D%0A%0D%0Alet%20a%20%3D%20createMyClass(ArrayClass%2C%20%22hop!%22)%3B%20%20%20%2F%2F%20type%3A%20%7BArrayClassAbstract%3Cstring%3E%7D%0D%0Aa.test()%3B%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%2F%2F%20Doesn't%20work...%20%22test%22%20doesn't%20exist%20in%20ArrayClassAbstract%0D%0A%0D%0A%0D%0Afunction%20createMyClass2%3CT%20extends%20ArrayClassAbstract%3Cstring%3E%3E%0D%0A%20%20%20%20(className%3A%20%7B%20new%20(element%3A%20string)%3A%20T%20%7D%2C%20element%3A%20string)%20%7B%20%0D%0A%20%20%20%20return%20new%20className(element)%3B%0D%0A%7D%0D%0A%0D%0Alet%20b%20%3D%20createMyClass2(ArrayClass%2C%20%22hop!%22)%3B%20%20%2F%2F%20type%3A%20%7BArrayClass%3Cany%3E%7D%0D%0Ab.test()%3B%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%2F%2F%20Works%20but%20array%20elements%20typed%20as%20%22any%22) . Pour faire simple, je recherche createMyClass2 pour retourner un objet typé ArrayClass<string> sans spécifier explicitement le type lors de son appel (inférence de type).
On peut considérer que ArrayClass et ArrayClassAbstract sont des sortes de plugins définis dans différents NPM par exemple.

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

Bon type générique, mauvais type de classe...

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"

Bon type de classe, mauvais type générique. Je ne sais pas s'il y a quelque chose de mal dans ma façon de déclarer la fabrique, ou si je suis victime des limitations de Typescript...

Une idée ?

1voto

Rodris Points 1339

Et si on utilisait des surcharges ?

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();

0voto

Ryan Cavanaugh Points 17393

Vous avez juste besoin d'un deuxième paramètre de type. Il peut toujours être déduit :

// 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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X