Общий метод для преобразования строки в тип enum - PullRequest
1 голос
/ 25 июня 2019

Мне нужно принять пользовательский ввод и подтвердить, что значение является допустимым значением перечисления.Например, у меня есть enum:

enum Gender {
  Male = 'MALE',
  Female = 'FEMALE',
  Neutral = 'NEUTRAL'
}

Пользователь может ввести туда любое значение, и я хотел бы утверждать, что это значение Gender, если значение является действительным Gender.

Было бы удобно иметь такую ​​функцию:

function toEnum<T, E extends typeof T>(enumType: E, value: string): T | null {
  if (Object.values(enumType).includes(value)) {
    return value as T;
  }

  return null;
}

Который я мог бы назвать так:

const a: Gender = toEnum(Gender, 'FEMALE'); // === Gender.Female
const b: Gender = toEnum(Gender, 'APPLE'); // null;

Я действительно не знаю, что общегоподпись имеет смысл:

function toEnum<T, E extends typeof T>(enumType: E, value: string): T | null { /* ... */ }

function toEnum<T>(enumType: typeof T, value: string): T | null { /* ... */ }

function toEnum<E, T instanceof E>(enumType: E, value: string): T | null { /* ... */ }

Я знаю, что эти вещи не действительны.Я просто пытаюсь выразить свое намерение.

В любом случае, каков наименьший хакерский способ заставить машинописный текст выводить, что тип T будет значением перечисления при сохранении безопасности типов?Есть ли лучший способ написать безопасную для типов функцию обратного отображения для строковых перечислений?

EDIT:

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

enum Gender {
  Male = 'MALE',
  Female = 'FEMALE',
  Neutral = 'NEUTRAL'
}

type Maybe<T> = T | null;

export function toEnum<T>(enumType: any, value: string): Maybe<T> {
  if (Object.values(enumType).includes(value)) {
    return value as unknown as T;
  }

  return null;
}

const shouldBeFemale: Maybe<Gender> = toEnum<Gender>(Gender, 'FEMALE'); // Gender.Female
const shouldBeNull: Maybe<Gender> = toEnum<Gender>(Gender, 'NOT VALID'); // null

// but this is lame
enum Fruit {
  Apple = 'APPLE',
  Orange = 'ORANGE'
}

const wtfItIsApple: Maybe<Gender> = toEnum<Gender>(Fruit, 'APPLE'); // 'APPLE' . . . wat!?
const validButStillSilly: Maybe<Gender> = toEnum<Gender>(Fruit, 'NOT VALID'); // null
const ughAny: Maybe<Gender> = toEnum<Gender>(217, ':('); // null

1 Ответ

2 голосов
/ 25 июня 2019
type IsEnumKey<T, E> = [E] extends [keyof T] ? true : false;
function toEnum<T, E extends (string | keyof T)>(enumType: T, value: E): IsEnumKey<T, E> extends true ? E : E | null {
  if (Object.values(enumType).includes(value)) {
    return value as any;
  }

  return null as any;
}

enum Fruit {
    APPLE = "APPLE",
    ORGANE = "ORANGE",
}

const testType = toEnum(Fruit, "APPLE") // Not or null just "Apple";
const testType1 = toEnum(Fruit, "") // "" | null

Дайте мне знать, если это будет сделано для вас, условные типы возврата, подобные этому, будут стоить того, чтобы приводить в теле метода (хотя, вероятно, избегать преобразования или использовать защиту типов), но получить дополнительный контроль над типом возврата. .

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

Неизвестные члены перечисления вернут свой тип union null.

РЕДАКТИРОВАТЬ: это решить?

function toEnum<T, E extends (string | keyof T)>(enumType: T, value: E): [E] extends [keyof T] ? T[E] : E | null {
  if (Object.values(enumType).includes(value)) {
    return value as any;
  }

  return null as any;
}

enum Fruit {
    APPLE = "APPLE",
    ORGANE = "ORANGE",
}

const testType = toEnum(Fruit, "APPLE") // works;
const testType1 = toEnum(Fruit, "") // "" | null
const testType2: Fruit.APPLE = toEnum(Fruit, "APPLE") // works

Окончательное редактирование: Вы можете обойти приведение в теле функции с перегрузкой вместо условных типов возврата, ниже приведено модифицированное более приятное решение, в котором прямое приведение больше не требуется в теле метода.

function toEnum<T, E extends keyof T>(enumType: T, value: E): T[E];
function toEnum<T, E extends string>(enumType: T, value: E): E | null;
function toEnum<T, E extends string>(enumType: T, value: E): E | null {
  if (Object.values(enumType).includes(value)) {
    return value as E;
  }

  return null;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...