TypeScript - проверка типа перечисления для условных типов?

У меня есть успокаивающие службы, которые принимают значения перечисления либо как число, либо как строку, но всегда возвращают только число. Есть ли способ их набрать?

Вот что я вроде как хочу, но это синтаксически неверно:

enum Features {
  "A" = 1,
  "B" = 2,
  "C" = 2
}

type EnumOrString<T> = T extends enum
  ? T | keyof T
  : T

declare function getData(featureFilter: EnumOrString<Features>[]): Features[]

getData принимает массив значений перечисления или ключей перечисления, но возвращает только значения перечисления.

Я также хотел бы распространить это на сопоставленные типы, такие как _3 _, чтобы все вложенные перечисления получали такую ​​обработку - без необходимости иметь отдельные иерархии типов, разделенных запросами и ответами.


person Jason Kleban    schedule 21.05.2018    source источник
comment
Я не думаю, что вы можете идентифицировать enum программно. Самое близкое, что я когда-либо получал, - это что-то вроде type IsEnum<T> = T extends Record<keyof T, string | number> ? true : false, но в любом случае вам нужно сделать EnumOrString<typeof Features>.   -  person jcalz    schedule 21.05.2018
comment
Подождите, так вы хотите что-то, что принимает структуру данных и рекурсивно изменяет Features на Features | keyof typeof Features?   -  person jcalz    schedule 21.05.2018
comment
ну, члены любого типа перечисления (рекурсивно) - но не все члены будут типизированы перечислением   -  person Jason Kleban    schedule 21.05.2018
comment
запрашивается в github.com/Microsoft/TypeScript/issues/24293   -  person Jason Kleban    schedule 21.05.2018
comment
Итак, в этом вопросе есть две части ... одна - определение перечислений, что, я думаю, вы не можете сделать. Другой - рекурсивная замена типа перечисления объединением значений перечисления и ключей объекта перечисления, что вы можете сделать, если заранее знаете, какие перечисления вас интересуют (например, Features).   -  person jcalz    schedule 21.05.2018
comment
Что ж, я почти уверен, что смогу создать рекурсивный тип, который делает это с учетом части 1 - я просто говорю, что ответ не может срезать углы, так что часть 2 была бы невозможна. См. Ссылку на пример в редактировании.   -  person Jason Kleban    schedule 21.05.2018


Ответы (1)


Я не думаю, что «идентифицировать enum» возможно прямо сейчас. Даже если бы вы могли, вы не можете программно преобразовать из типа Features (который является элементом перечисления Features) в тип typeof Features (отображение из ключей в Features elements), не зная в первую очередь о Features. Опять же: тип Features.B, например, ничего не знает о строковом литерале "B". Только typeof Features имеет свойство, подобное {B: Features.B}. Если вы хотите, чтобы функция типа конвертировала из Features в Features | keyof typeof Features, вам нужно указать typeof Features явно. Так что даже если бы у вас была extends enum нотация вашей мечты, вам все равно нужно было бы написать код замены со списком отображений, которые вам небезразличны. Извините.


Обращаясь только к части рекурсии, если это имеет значение, вот как я рекурсивно обработал бы тип, чтобы заменить известное значение перечисления объединением значений перечисления и соответствующих ключей:

enum Features {
  "A" = 1,
  "B" = 2,
  "C" = 2
}
type ValueOf<T> = T[keyof T]
type FeatureKey<T extends Features> =
  Extract<ValueOf<{
    [K in keyof typeof Features]: [K, typeof Features[K]]
  }>, [any, T]>[0]

type DeepFeaturesOrKey<T> =
  T extends Features ? (T | FeatureKey<T>) :
  T extends Array<infer L> ? DeepFeaturesOrKeyArray<L> :
  T extends object ? { [K in keyof T]: DeepFeaturesOrKey<T[K]> } : T

interface DeepFeaturesOrKeyArray<L> extends Array<DeepFeaturesOrKey<L>> { }

Сложные биты извлекают подмножество перечисления, если вы не укажете все (например, вы используете размеченное объединение с ключом определенного значения перечисления), и, конечно же, весь трюк с глубоким массивом, чтобы избежать упомянутого ужасного сообщения об ошибке" циклической ссылки "здесь:

Подобно типам объединения и пересечения, условным типам не разрешается ссылаться на себя рекурсивно (однако разрешены косвенные ссылки через типы интерфейса или типы литералов объекта)

Давайте протестируем это:

interface Foo {
  bar: string,
  baz: Features,
  qux: {
    a: Features[],
    b: boolean
  },
  quux: Features.A,
  quuux: Features.B  
}

type DeepFeaturesOrKeyFoo = DeepFeaturesOrKey<Foo>
declare const deepFeaturesOrKeyFoo: DeepFeaturesOrKeyFoo
deepFeaturesOrKeyFoo.bar; // string
deepFeaturesOrKeyFoo.baz; // Features | "A" | "B" | "C"
deepFeaturesOrKeyFoo.qux.a[1];  // Features | "A" | "B" | "C"
deepFeaturesOrKeyFoo.qux.b; // boolean
deepFeaturesOrKeyFoo.quux; // Features.A | "A"
deepFeaturesOrKeyFoo.quuux; // Features.B | "B" | "C" 
// note that the compiler considers Features.B and Features.C to be the
// same value, so this becomes Features.B | "B" | "C"

Выглядит неплохо. Надеюсь, это поможет.

person jcalz    schedule 21.05.2018