typescript2.0 - Använd strängen enumvärde i TypeScript-gränssnitt som en beräknad egenskapsnyckel


Jag vill definiera en funktion som returnerar olika typer av objekt baserat på vilken nyckel jag anger. Det är i princip som tricket som används här i createElement-funktionen

https://github.com/Microsoft/TypeScript/blob/master/lib/lib.dom.d.ts#L3117

Men istället för en strängliteral vill jag använda en strängenumtyp istället. Så jag skrev något som detta

class Dog {}
class Cat {}
class Bird {}

enum Kind {
  Dog = 'Dog',
  Cat = 'Cat',
  Bird = 'Bird'
}

interface KindMap {
  [Kind.Dog]: Dog
  [Kind.Cat]: Cat
  [Kind.Bird]: Bird
}

function getAnimal<K extends keyof KindMap> (key: K): KindMap[K] {
  switch (key) {
    case Kind.Dog:
      return new Dog()
    case Kind.Cat:
      return new Cat()
    case Kind.Bird:
      return new Bird()
  }
}

Men TypeScript verkar inte gilla sättet jag satte värdet för enum Kind i gränssnittet som en beräknad egenskap, det klagar

A computed property name in an interface must directly refer to a built-in symbol.

Här kommer frågan, jag har redan konstanterna definierade i enum, jag gillar inte att använda strängliteraler, finns det ett sätt jag kan få detta att fungera? Vilket betyder att använda Kind-enums värde som nyckel för den beräknade egenskapen i KindMap.

Svar #1

Ibland är ett enkelt objekt med keyof enklare än ett enum i TypeScript:

class Dog { }
class Cat { }

const kindMap = {
    Dog,
    Cat
};

type KindMap = typeof kindMap;

function getAnimalClass<K extends keyof KindMap>(key: K): KindMap[K] {
    return kindMap[key];
}

// These types are inferred correctly
const DogClass = getAnimalClass('Dog'); 
const dog = new DogClass();

const CatClass = getAnimalClass('Cat');
const cat = new CatClass();

Prova det i TypeScript Playgrounds

Jag försökte implementera getAnimal() med mappade typer men verkade stöta på buggig inferens. Men att slå upp klassen var enklare.

Att inline:a getAnimalClass-uppslaget fungerar också med typavledning:

const dog = new kindMap['Dog']();

Svar #2

Den ursprungliga koden fungerar nästan som den är i TypeScript 2.7.2, även om den har ett fel eftersom andra värden än de 3 Kind kan skickas in.

Du kan säga att det är möjligt att returnera undefined:

function getAnimal<K extends keyof KindMap>(key: K): undefined | KindMap[K] {
    switch (key) {
        case Kind.Dog:
            return new Dog()
        case Kind.Cat:
            return new Cat()
        case Kind.Bird:
            return new Bird()
        default:
            return undefined;
    }
}

Jag skulle förmodligen bara använda en karta från enum till klasskonstruktören direkt, eftersom användning av den enkelt kan härleda typen:

const kindMap = {
    [Kind.Dog]: Dog,
    [Kind.Cat]: Cat,
    [Kind.Bird]: Bird,
}

const tweeter = new kindMap[Kind.Bird]();

Om du vill att funktionen ska instansiera objekten kan du också ställa in ett parallellt gränssnitt:

interface KindMap {
    [Kind.Dog]: Dog,
    [Kind.Cat]: Cat,
    [Kind.Bird]: Bird,
}

const getAnimal = <K extends Kind>(k: K): KindMap[K] => new kindMap[k]();

const meower = getAnimal(Kind.Cat);

Relaterade artiklar