Разобрать строку как Typescript Enum - PullRequest
0 голосов
/ 17 сентября 2018

Учитывая перечисление, которое выглядит так:

export enum UsedProduct {
    Yes = 'yes',
    No = 'no',
    Unknown = 'unknown',
}

Я хотел бы написать функцию, которая принимает набор строковых литералов и возвращает экземпляр UsedProduct. Пока что я написал такую ​​функцию:

export function parseUsedProduct(usedProdStr: 'yes' | 'no' | 'unknown'): UsedProduct {
    switch (usedProdStr) {
        case 'yes':
            return UsedProduct.Yes;
        case 'no':
            return UsedProduct.No;
        case 'unknown':
            return UsedProduct.Unknown;
        default:
            return unknownUsedProductValue(usedProdStr);
    }
}

function unknownUsedProductValue(usedProdStr: never): UsedProduct {
    throw new Error(`Unhandled UsedProduct value found ${usedProdStr}`);
}

Эта реализация не очень хороша, потому что я должен переопределить возможные значения перечисления. Как я могу переписать эту функцию, чтобы мне не приходилось определять 'yes' | 'no' | 'unknown'?

Ответы [ 2 ]

0 голосов
/ 17 сентября 2018

TypeScript не делает это легким для вас, поэтому ответ не является однострочным.

Значение enum, такое как UsedProduct.Yes, - это просто строковый или числовой литерал во время выполнения (в данном случае строка "yes"), но во время компиляции оно обрабатывается как подтип строка или числовой литерал. Итак, UsedProduct.Yes extends "yes" верно. К сожалению, учитывая тип UsedProduct.Yes, нет никакого программного способа расширить тип до "yes" ... или, учитывая тип UsedProduct, нет никакого программного способа расширить его до "yes" | "no" | "unknown". В языке отсутствуют некоторые функции, которые вам понадобятся для этого.

Там есть способ сделать сигнатуру функции, которая ведет себя как parseUsedProduct, но она использует generics и условные типы для достижения этого:

type Not<T> = [T] extends [never] ? unknown : never
type Extractable<T, U> = Not<U extends any ? Not<T extends U ? unknown : never> : never>

declare function asEnum<E extends Record<keyof E, string | number>, K extends string | number>(
  e: E, k: K & Extractable<E[keyof E], K>
): Extract<E[keyof E], K>

const yes = asEnum(UsedProduct, "yes"); // UsedProduct.yes
const no = asEnum(UsedProduct, "no"); // UsedProduct.no
const unknown = asEnum(UsedProduct, "unknown"); // UsedProduct.unknown
const yesOrNo = asEnum(UsedProduct, 
  Math.random()<0.5 ? "yes" : "no"); // UsedProduct.yes | UsedProduct.no

const unacceptable = asEnum(UsedProduct, "oops"); // error

Обычно он принимает тип объекта enum E и тип строки или числа K и пытается извлечь значение (я) свойства E, которые расширяют K. Если значения E extended K отсутствуют (или если K является типом объединения, в котором одна из частей не соответствует никакому значению E), компилятор выдаст ошибку. Особенности работы Not<> и Extractable<> доступны по запросу.

Что касается реализации функции, вам, вероятно, потребуется использовать утверждение типа . Что-то вроде:

function asEnum<E extends Record<keyof E, string | number>, K extends string | number>(
  e: E, k: K & Extractable<E[keyof E], K>
): Extract<E[keyof E], K> {
  // runtime guard, shouldn't need it at compiler time
  if (Object.values(e).indexOf(k) < 0)
    throw new Error("Expected one of " + Object.values(e).join(", "));
  return k as any; // assertion
}

Это должно сработать. В вашем конкретном случае мы можем жестко кодировать UsedProduct:

type Not<T> = [T] extends [never] ? unknown : never
type Extractable<T, U> = Not<U extends any ? Not<T extends U ? unknown : never> : never>
function parseUsedProduct<K extends string | number>(
  k: K & Extractable<UsedProduct, K>
): Extract<UsedProduct, K> {
  if (Object.values(UsedProduct).indexOf(k) < 0)
    throw new Error("Expected one of " + Object.values(UsedProduct).join(", "));
  return k as any;
}

const yes = parseUsedProduct("yes"); // UsedProduct.yes
const unacceptable = parseUsedProduct("oops"); // error

Надеюсь, это поможет. Удачи!

0 голосов
/ 17 сентября 2018

Вы можете использовать getKeyOrThrow -метод из ts-enum-util.Не уверен, как это реализовано, но вы можете посмотреть на это здесь .

Вот блик стека Я сделал, чтобы продемонстрировать использование в вашем случае.

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