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
Надеюсь, это поможет. Удачи!