typescript - Generisk begränsning av formen `<T extends E>` där `E` är en `enum`?


Vad betyder något som:

function f<T extends E>(obj: T) {}

där E är någon enum som, säg

enum E {
    X
}

Eftersom enum faktiskt inte kan utökas, kan inte denna begränsning tas bort och funktionen skrivas om till:

function f(obj: E) {}

PS: detta är koden i fråga.

Svar #1

Om du inte behöver typen T i din funktion, så har du rätt att den inte behöver vara generisk.

Men i filen du länkade används typen inom klassen.

Här är de relevanta delarna, med annat stuff borttaget:

abstract class ComputedEditorOption<K1 extends EditorOption, V> implements IEditorOption<K1, V> {

    public readonly id: K1;

    constructor(id: K1, deps: EditorOption[] | null = null) {
        this.id = id;
        //...
    }
}

Detta säger att en id av typen EditorOption skickas till konstruktören. Detta sparas i den generiska klassen typen K1. Senare, när du får åtkomst till id-egenskapen, kommer den att returnera samma enum-alternativ som skickades till konstruktören. Detta kräver generics för att kunna fånga en underklass av ett enum (ett specifikt värde av det enum), och använda det i returvärdet av den funktionen, eller instansen av en klass.


För att klargöra med ditt eget exempel:

function f1(arg: MyEnum): { id: MyEnum } {
    return { id: arg }
}

const a1 = f1(MyEnum.A) // Type: MyEnum
const b1 = f1(MyEnum.B) // Type: MyEnum

När du skriver argumentet som MyEnum här kan du inte använda det enum som angavs i returtypen, så det bästa du kan göra är att skriva returvärdet som MyEnum, eftersom du inte kan veta vilket värde av det enum som gavs.

Men när du använder en generisk:

function f2<T extends MyEnum>(arg: T): { id: T } {
    return { id: arg }
}

const a2 = f2(MyEnum.A) // Type: MyEnum.A
const b2 = f2(MyEnum.B) // Type: MyEnum.B

Nu kan du använda exakt den angivna typen av argumentet i returtypen.

Detta är, mer eller mindre, vad koden du länkade gör.

Lekplats

Svar #2

Om en typ utökar en enum, betyder det att typen kan vara:

  • ett specifikt värde av enum
  • någon delmängd av värdena i uppräkningsvärdet
  • alla möjliga värden av enum

Låt oss säga att vår enum har tre värden:

enum E {
    X,
    Y,
    Z
}

function f<T extends E>(val: T) {}

När du anropar funktionen f kan du bara skicka ett värde av E åt gången. Men det betyder inte att den generiska T bara kan vara ett värde av E. Om du manuellt specificerar den generiska T, är alla dessa giltiga:

f<E>(E.X);
f<E.X>(E.X);
f<E.X | E.Y>(E.X);
f<Exclude<E, E.Y>>(E.X);

I varje fall är E.X tilldelningsbar till T (det vill säga, E.X är lika med eller smalare än T) och T extends E (det vill säga, T är lika med eller smalare än E).

Lekplatslänk