typescript - Cum pot să scriu o funcție generică care folosește tipul unui membru al unui enum în locul enumului în sine?


Documentația TypeScript despre enumerări spune:

membrii enum devin de asemenea și tipuri!

Aș dori să am o funcție care să ia o valoare generică care este un membru al unui enum și să folosească tipul membrului specific al enum-ului.

Văd că pot folosi membrul enum ca tip:

enum Kind {
    Apple,
    Banana,
}

type Example1 = Kind.Apple;

Pot folosi membrul enum într-un context de tip generic:

interface Wrapper<K> {
    kind: K;
}

type Example2 = Wrapper<Kind.Apple>;

Când trec membrul enum la o funcție, acesta își pierde faptul că este un membru specific al enum și în schimb devine tipul enum-ului părintelui:

const returned = <K>(kind: K): Wrapper<K> => ({
    kind,
})

const theValue = returned(Kind.Apple);

// Type 'Kind' is not assignable to type 'Kind.Apple'.
const butNot: Example2 = theValue;

Teren de joacă

Răspuns #1

Motivul pentru care tipul parametrului kind este lărgit de la Kind.Apple la Kind este explicat în https://github.com/Microsoft/TypeScript/pull/10676 :

În timpul inferenței argumentului de tip pentru o expresie de apel, tipul inferat pentru un parametru de tip T este largit la tipul său literal extins dacă:

  • toate inferențele pentru T au fost făcute pentru aparițiile de nivel superior ale lui T în cadrul tipului de parametru respectiv, și
  • T nu are nicio constrângere sau constrângerea sa nu include tipuri primitive sau literale, și
  • T a fost fixat în timpul inferenței sau T nu apare la nivel superior în tipul returnat.

Dacă, așa cum se menționează în al doilea punct, adăugați o constrângere pentru parametrul generic T, acesta va începe să deducă tipul literal specific. Prețul pentru asta este că face ca returned să fie aplicabil doar subtipurilor lui Kind.

enum Kind {
    Apple,
    Banana,
}

interface Wrapper<K> {
    kind: K;
}

const returned = <K extends Kind>(kind: K): Wrapper<K> => ({
    kind,
})

const theValue = returned(Kind.Apple);

const thisWorksNow: Wrapper<Kind.Apple> = theValue;

Răspunsul #2

Poți specifica în mod explicit ce ar trebui să fie tipul generic la apelul returned:

enum Kind {
    Apple,
    Banana,
}

interface Wrapper<K> {
    kind: K;
}

const returned = <K>(kind: K): Wrapper<K> => ({
    kind,
})

const theValue = returned<Kind.Apple>(Kind.Apple);

const thisWorksNow: Wrapper<Kind.Apple> = theValue;