javascript - TypeScript: grensesnitt polymorfisme problem


Jeg har et grunnleggende Account-grensesnitt:

interface Account {
  id: number;
  email: string;
  password: string;
  type: AccountType;
}

hvor AccountType:

enum AccountType {
  Foo = 'foo',
  Bar = 'bar'
}

og to kontosubtyper (FooAccount og BarAccount) som utvider Account-grensesnittet:

interface FooAccount extends Account {
  foo: Foo;
}
interface BarAccount extends Account {
  bar: Bar;
}

Account er en aggregat som inneholder grunnleggende kontoinformasjon og, avhengig av typen, eier et Foo- eller et Bar-objekt.

Handlinger på disse objektene kan bare utføres av eierne deres (kontoen).

Jeg har definert et AccountRepository:

export interface AccountRepository {
  findById(accountId: number): Account;
}

hvor findById(accountId: number) returnerer en Account, men denne kontoen kan være en hvilken som helst FooAccount eller BarAccount.

Jeg vil bruke denne findById-funksjonen før jeg utfører noen handling på en Foo eller Bar. For eksempel, la oss si at jeg vil oppdatere en kontos Foo:

  • ville bruke findById(accountId: number) for å hente kontoen
  • sjekk AccountType for kontoen, i dette tilfellet account.type === AccountType.Foo
  • hvis AccountType-sjekken er korrekt, kan du deretter få tilgang til account.foo.id og bruke den fooId for å utføre den ønskede oppdateringen

Problemet her er at dette siste punktet feiler: ettersom findById(accountId: number): Account returnerer en Account og det ikke finnes en foo: Foo egenskap definert i dens grensesnitt.

Jeg har også prøvd følgende, men det lar seg heller ikke gjøre:

const fooAccount: FooAccount = findById(accountId);

fordi funksjonen returnerer en Account.

Jeg prøver å finne ut hvordan dette kan oppnås, hva går jeg glipp av? Er det noe jeg kan gjøre galt?

Svar #1

Den beste løsningen er sannsynligvis å bruke en diskriminert union.

export class Bar { public idBar: number; }
class Foo { public idFoo: number; }
interface AccountCommon {
  id: number;
  email: string;
  password: string;
}

enum AccountType {
  Foo = 'foo',
  Bar = 'bar'
}

interface FooAccount extends AccountCommon {
  type: AccountType.Foo; // type can only be Foo
  foo: Foo;
}
interface BarAccount extends AccountCommon {
  type: AccountType.Bar; // type can only be Bar
  bar: Bar;
}
// The discriminated union
type Account = BarAccount | FooAccount //type is common so type can be either Foo or Bar

export interface AccountRepository {
  findById(accountId: number): Account;
}

let r: AccountRepository;

let a = r.findById(0);
if (a.type === AccountType.Bar) { // type guard
  a.bar.idBar // a is now BarAccount
} else {
  a.foo.idFoo // a is now FooAccount
}

Svar #2

Løst dette ved å bruke Type Assertion, ved bare å legge til as FooAccount slik:

const fooAccount: FooAccount = findById(accountId) as FooAccount;

Det er ikke nødvendig å endre det eksisterende designet for å oppnå det.

I utgangspunktet lykkes påstanden fra type S til T hvis enten S er en subtype av T eller T er en subtype av S.

Mer info: https://basarat.gitbooks.io/typescript/docs/types/type-assertion.html