Typescriptin uusi luokka parametrien perusteella


class A {
   public aCall(a: any, payload: string) {}
   public bCall(a: any, payload: number) {}
   public cCall(a: any) {}
   .
   .
   .
}

function createNewClass(aCtor: A) {
  // load all of the A method and remove first params
  // generic code on here
  // Final result should be like this
  return class B {
    public aCall(payload: string) {}
    public bCall(payload: number) {}
  }
}

// C.d.ts
interface C extends createNewClass(A) {}

Voinko saada funktion (tai metodin sisäisen dekorointifunktion), joka arvioi tulevan luokan ja luo uuden luokan poistamalla kaikki ensimmäiset parametrit, jotta voin käyttää uutta luokkaa laajentamiseen, vai eikö sitä voi tehdä

Vastaus #1

Katso alla oleva 3.0 vastaus

Voit käyttää samanlaista lähestymistapaa kuin tässä vastauksessa. Sinun täytyy korvata konstruktorin paluuarvotyyppi ja käyttää muunnettua tyyppiä luodaksesi uusia funktioita, jotka ohittavat ensimmäisen argumentin:

type RemoveFirstArg<TCtor extends new (... args: any[]) => any > = ReplaceInstanceType<TCtor,  { [P in keyof InstanceType<TCtor>]: RemoveArg<InstanceType<TCtor>[P]> }>
function createNewClass<TCtor extends new (... args: any[]) => any >(aCtor: TCtor) : RemoveFirstArg<TCtor>{
    // load all of the A method and remove first params
    return null as any;
}

type IsValidArg<T> = T extends object ? keyof T extends never ? false : true : true;
type RemoveArg<T> = T extends (a: infer A, b: infer B, c: infer C, d: infer D, e: infer E, f: infer F, g: infer G, h: infer H, i: infer I, j: infer J) => infer R ? (
    IsValidArg<J> extends true ? (b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I, j: J) => R :
    IsValidArg<I> extends true ? (b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I) => R :
    IsValidArg<H> extends true ? (b: B, c: C, d: D, e: E, f: F, g: G, h: H) => R :
    IsValidArg<G> extends true ? (b: B, c: C, d: D, e: E, f: F, g: G) => R :
    IsValidArg<F> extends true ? (b: B, c: C, d: D, e: E, f: F) => R :
    IsValidArg<E> extends true ? (b: B, c: C, d: D, e: E) => R :
    IsValidArg<D> extends true ? (b: B, c: C, d: D) => R :
    IsValidArg<C> extends true ? (b: B, c: C) => R :
    IsValidArg<B> extends true ? (b: B) => R :
    IsValidArg<A> extends true ? () => R :
    T
) : never

type ReplaceInstanceType<T, TNewReturn> = T extends new (a: infer A, b: infer B, c: infer C, d: infer D, e: infer E, f: infer F, g: infer G, h: infer H, i: infer I, j: infer J) => infer R ? (
    IsValidArg<J> extends true ? new (a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I, j: J) => TNewReturn :
    IsValidArg<I> extends true ? new (a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I) => TNewReturn :
    IsValidArg<H> extends true ? new (a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H) => TNewReturn :
    IsValidArg<G> extends true ? new (a: A, b: B, c: C, d: D, e: E, f: F, g: G) => TNewReturn :
    IsValidArg<F> extends true ? new (a: A, b: B, c: C, d: D, e: E, f: F) => TNewReturn :
    IsValidArg<E> extends true ? new (a: A, b: B, c: C, d: D, e: E) => TNewReturn :
    IsValidArg<D> extends true ? new (a: A, b: B, c: C, d: D) => TNewReturn :
    IsValidArg<C> extends true ? new (a: A, b: B, c: C) => TNewReturn :
    IsValidArg<B> extends true ? new (a: A, b: B) => TNewReturn :
    IsValidArg<A> extends true ? new (a: A) => TNewReturn :
    new () => TNewReturn
) : never

//Usage
class A {
    public aCall(a: any, payload: string) { }
    public bCall(a: any, payload: number) { }
}

// Extending a class
class C extends createNewClass(A) { }

new C().aCall('xxx')

//For interfaces we can just use the type
interface IC extends RemoveFirstArg<typeof A> { }

Huomautus Syy moniin, moniin samanlaisiin riveihin on se, että meidän on uudelleensijoitettava kukin konstruktori/funktio tietyn määrän argumentteja varten. Yllä oleva toteutus toimii 10 argumentille, mikä pitäisi riittää, mutta lisää voidaan lisätä.

Muokkaa

Koska alkuperäiseen kysymykseen on vastattu, TypeScript on parantanut ratkaisumahdollisuuksia tähän ongelmaan. Tuplien lisäämisen myötä rest-parametreihin ja spread-lausekkeisiin meidän ei enää tarvitse tehdä kaikkia ylikuormituksia RemoveArg- ja ReplaceInstanceType-tyypeille:

type IsValidArg<T> = T extends object ? keyof T extends never ? false : true : true;

type ArgumentTypes<T> = T extends (... args: infer U ) => any ? U: never;
type ReplaceInstanceType<T, TNewInstance> = T extends new (...args: any[])=> infer R ? new (...a: ArgumentTypes<T>) => TNewInstance : never;

type ArgumentTypesSkipOne<T> = T extends (a: any, ... args: infer U ) => any ? U: never;
type RemoveArg<T> = T extends (a: any, ...args: any[])=> infer R ? (...a: ArgumentTypesSkipOne<T>) => R : T;

type RemoveFirstArg<TCtor extends new (... args: any[]) => any > = ReplaceInstanceType<TCtor,  { [P in keyof InstanceType<TCtor>]: RemoveArg<InstanceType<TCtor>[P]> }>

function createNewClass<TCtor extends new (... args: any[]) => any >(aCtor: TCtor) : RemoveFirstArg<TCtor>{
    // load all of the A method and remove first params
    return null as any;
}

Tämä ei ole ainoastaan lyhyempi, vaan se ratkaisee myös useita ongelmia

  • Valinnaiset parametrit pysyvät valinnaisina
  • Argumenttien nimet säilytetään
  • Toimii minkä tahansa määrän argumentteja varten

Vastaus #2

Jos jostain syystä välität siitä, että todella yrität toteuttaa tätä asiaa, voisit tehdä jotain seuraavanlaista. Huomaa, että aion korvata vain metodit, joilla on kaksi argumenttia. Jos sinun tarvitsee käsitellä kaikkia metodeja, kirjoittaminen olisi pidettävä monimutkaisempana, kuten @TitianCernicova-Dragomirin vastauksessa:

type RemoveFirstArgOfTwoArgMethods<T> = { [K in keyof T]:
  T[K] extends (a: any, payload: infer P) => infer R ? (payload: P) => R : T[K];
}

function createNewClass<T>(aCtor: new (...args: any[]) => T): new (...args: any[]) => RemoveFirstArgOfTwoArgMethods<T> {

  const B = (class extends (aCtor as any) {}) as new (...args: any[]) => RemoveFirstArgOfTwoArgMethods<T>;

  // you will need to actually decide what that first argument will be
  const firstVal: any = "whoKnows";

  Object.keys(aCtor.prototype).forEach(k => {
    const protoVal = (aCtor.prototype)[k];
    if ((typeof protoVal === 'function') && (protoVal.length === 2)) {
      B.prototype[k] = function (...args: any[]) { return (protoVal as Function).call(this, firstVal, ...args) }
    }
  })

  return B;
}

Ajatuksena on, että se laajentaa alkuperäistä luokkaa, mutta korvaa sen kahden argumentin metodit uusilla yhden argumentin metodeilla, jotka kutsuvat alkuperäistä metodia vakioidulla ensimmäisellä argumentilla (tässä tapauksessa se on merkkijono "whoKnows", mutta saatat haluta jotain muuta).

Voit tarkistaa, että yllä oleva toimii:

class A {
  public aCall(a: any, payload: string) {
    console.log("aCall(" + a + "," + payload + ")");
  }
}

const a = new A();
a.aCall("explicit", "call"); // aCall(explicit, call);

const C = createNewClass(A);
const c = new C();
c.aCall("implicit"); // aCall(whoKnows, implicit);

Todennäköisesti tällaisilla luokilla pelaamiseen liittyy kaikenlaisia varoituksia, joten ole varovainen ja varmista, että ymmärrät todella käyttötapauksesi ja mitä tapahtuu, kun kohtaat käyttäytymistä, joka ei noudata sitä.

Toivottavasti siitä on apua. Onnea!