Общее ограничение вида «, где `E` — это `перечисление`?

#typescript #generics #enums #typescript-generics

#typescript #универсальные #перечисления #typescript-generics

Вопрос:

В чем смысл чего-то вроде:

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

где E enum , скажем, что-то вроде

 enum E {
    X
}
  

Поскольку enum s на самом деле не может быть расширен, нельзя ли удалить это ограничение и переписать функцию в:

 function f(obj: E) {}
  

PS: это тот код, о котором идет речь.

Ответ №1:

Если вам не нужен тип T в вашей функции, то вы правы, он не должен быть универсальным.

Однако в файле, который вы связали, тип используется внутри класса.

Вот соответствующие биты, а другие вещи обрезаны:

 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;
        //...
    }
}
  

Это говорит о том, что id EditorOption в конструктор передается тип. Это сохраняется в типе универсального класса K1 . Позже, когда вы получите доступ к id свойству, оно вернет тот же параметр enum, который был передан конструктору. Для этого требуются универсальные методы, чтобы захватить подтип перечисления (конкретное значение этого перечисления) и использовать его в возвращаемом значении этой функции или экземпляра класса.


Для пояснения на вашем собственном примере:

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

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

Когда вы вводите аргумент как MyEnum здесь, вы не можете использовать перечисление, которое было указано в возвращаемом типе, поэтому лучшее, что вы можете сделать, это ввести возвращаемое значение как MyEnum , потому что вы не можете знать, какое значение этого перечисления было предоставлено.

Но когда вы используете общий:

 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
  

Теперь вы можете использовать точно указанный тип аргумента в возвращаемом типе.

Это, более или менее, то, что делает связанный вами код.

Игровая площадка

Ответ №2:

Если тип расширяет перечисление, это означает, что тип может быть:

  • одно конкретное значение перечисления
  • некоторое подмножество значений перечисления
  • все возможные значения перечисления

Допустим, что наше перечисление имеет три значения:

 enum E {
    X,
    Y,
    Z
}

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

При вызове функции f вы можете передавать ей только одно значение E за раз. Но это не означает, что общим T может быть только одно значение E . Если вручную указать универсальное T , все они действительны:

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

В каждом случае E.X присваивается T (то E.X есть равно или более узко, чем T ) и T extends E (то T есть равно или более узко, чем E ).

Ссылка на игровую площадку