#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
).