node.js - Typescript: Overstyre statisk fabrikkmetode til foreldreklasse i barneklassemetode


Jeg støter på noen problemer med avhengighetsinjeksjon i Typescript. På hver klasse legger jeg til en fabrikkmetode som statisk hvor alle avhengigheter settes. Jeg gjør dette for testformål slik at jeg fortsatt kan bruke TDD-tilnærmingen.

Nå støter jeg på noen problemer med å overstyre fabrikkmetoden til foreldrekassen i en barnekasse. Eksempel:

interface DepsA {
  a: string
}

interface DepsB extends DepsA {
  b: Child1
}

class Parent {
  constructor(protected deps: DepsA | DepsB) {}

  public static factory<T extends Parent>() {
    return new this({a: 'This is a nice Dependency'}) as T
  }
}

class Child1 extends Parent {}

class Child2 extends Parent {
  public static factory() {
    return new this({a: 'This is a nice Dependency', b: Child1.factory()})
  }
}

const child1 = Child1.factory<Child1>()
const child2 = Child2.factory()

Feilen jeg mottar er:

[ts]
Class static side 'typeof Child2' incorrectly extends base class static side 'typeof Parent'.
  Types of property 'factory' are incompatible.
    Type '() => Child2' is not assignable to type '<T extends Parent>() => T'.
      Type 'Child2' is not assignable to type 'T'.

Jeg vet hvorfor jeg får feilen, men har på dette tidspunktet ingen idé lenger om hvordan jeg skal fikse det, annet enn å gi nytt navn til fabrikksmetoden i Child2.

OPPDATERING: En relatert feilrapport til dette problemet, som forklarer automatisk hvorfor jeg bruker en Generisk på fabrikksmetoden, er: #26298

Svar #1

Først finnes det en forhåndsdefinert betinget type kalt InstanceType som kan hjelpe deg med å utlede klassetypen fra det statiske medlemmet:

public static factory<T extends typeof Parent>(this: T) {
    return new this({ a: 'This is a nice Dependency' }) as InstanceType<T>
}

For det andre, hvis du overstyrer en metode, statisk eller ikke, i en barneklasse, bør den ha en kompatibel signatur, inkludert de generiske tingene.

Følgelig kan kodeblokken din se slik ut (se i Typescript Playground):

interface DepsA {
    a: string
}

interface DepsB extends DepsA {
    b: Child1
}

class Parent {
    constructor(public deps: DepsA | DepsB) {}

    public static factory<T extends typeof Parent>(this: T) {
        return new this({ a: 'This is a nice Dependency' }) as InstanceType<T>
    }
}

class Child1 extends Parent {}

class Child2 extends Parent {
    public static factory<T extends typeof Parent>(this: T) {
        return new this({a: 'This is a nice Dependency', b: Child1.factory()}) as InstanceType<T>
    }
}

const child1 = Child1.factory()  // Type: Child1
const child2 = Child2.factory()  // Type: Child2

Derfra vil det også være mulig å returnere riktig deps-type, i stedet for en union, i ikke-statiske medlemmer, ved å bruke as this["deps"]. Men du må omstrukturere koden din litt.

Håper det hjelper ;-)

Relaterte artikler