Я столкнулся с проблемой, которую не могу решить в TS. И мне бы очень хотелось, чтобы кто-нибудь указал мне правильное направление. У меня есть API, который возвращает серию значений перечисления, и для работы с ними я смоделировал их в Typescript следующим образом:
enum condition = {NEW, USED}
Но при попытке работать с данными из API мне нужно указать на них, например typeof keyof condition
, и получить доступ к condition[0]
(в данном случае эквивалентно condition[condition[NEW]]
, что приводит к сообщению об ошибке).
Argument of type 'string' is not assignable to parameter of type '"NEW" | "USED"'.(2345)
Typescript экспортирует объект условия как
var condition;
(function (condition) {
condition[condition["NEW"] = 0] = "NEW";
condition[condition["USED"] = 1] = "USED";
})(condition || (condition = {}));
;
Это означает, что condition.NEW
равен 0 и condition[0]
НОВЫЙ. Я попытался принудительно указать тип, передав его с помощью as keyof typeof condition
следующим образом:
enum condition {NEW, USED};
function test(param: keyof typeof condition) {
console.info(param);
}
test(condition[condition.NEW]); // Fails linting, should pass
test(condition[condition.NEW] as keyof typeof condition); // Lint success
test('a' as keyof typeof condition); // Lint success, should fail
(Ссылка на детскую площадку: Детская площадка )
Но в лучшем случае это выглядит как хак, поскольку по сути он игнорирует передаваемый ему тип. Я бы беспокоился, что теперь передача недопустимой строки не будет правильно обнаружена. Как я могу заставить TS считать test(condition[condition.NEW]);
действительным и test('a' as keyof typeof condition);
недействительным?
Предположительно, API не может возвращать значения перечисления TypeScript, он возвращает что-то еще, что вы хотите выразить как тип перечисления TypeScript. enum condition {NEW = 'NEW', USED = 'USER'};
работает для вашего случая, если он действительно возвращает строки набора значений. Также вы можете просто сказать type condition = 'NEW'|'USED'
, чтобы указать, что он возвращает строку, которая может быть любым из этих двух значений.
Это ms/TS#38806 и ms/TS#50933. Это недостающая функция, и вам придется обойти ее, например, выполнив утверждение типа, которое вы делаете (при условии, что вы действительно хотите использовать обратные сопоставления и не хотите полностью избегать таких перечислений). Это полностью решает вопрос? Если да, то я напишу ответ с объяснением; если нет, то что мне не хватает?
@jcalz Похоже, это действительно ошибка, а не то, что я делаю неправильно. Думаю, в этом случае использование сопоставлений строк имеет смысл.
Предполагая, что API использует строки для выражения значений перечисления вместо чисел, вам, вероятно, лучше просто определить перечисление строк :
enum Condition {
NEW = 'NEW',
USED = 'USED'
};
function test(param: Condition) {
console.info(param);
}
test(Condition.NEW);
test(Condition[Condition.NEW]);
test(Condition['NEW']);
Вы столкнулись с недостающей функцией TypeScript, запрошенной по адресу microsoft/TypeScript#38806 и microsoft/TypeScript#50933 . Числовые перечисления имеют обратные сопоставления , где вы можете индексировать объект перечисления с числовым значением перечисления и получить обратно соответствующий строковый ключ. Но система типов не очень строго кодирует это обратное отображение; вместо конкретных пар значение-ключ он просто дает объекту перечисления числовую индексную подпись, тип свойства которой равен string
. Таким образом, хотя Condition[0]
оценивается как "NEW"
во время выполнения, TypeScript знает только, что это тип string
, чего недостаточно для ваших целей.
До тех пор, пока не будет реализована более строгая обратная типизация, вам придется обойти это. Вы всегда можете написать свою собственную служебную функцию, которая обеспечивает такую типизацию:
function strongReverseEnumMapping<const T extends Record<keyof T, PropertyKey>>(e: T) {
return e as
{ readonly [K in string & keyof T]: T[K] } &
{ readonly [K in keyof T as T[K] extends number ? T[K] : never]: K };
}
Здесь strongReverseEnumMapping()
просто возвращает введенные данные во время выполнения, но тип утверждается как более сильный, как пересечение части объекта перечисления, где числовые значения переназначаются на ключи и ключи переназначаются к ценностям.
Затем вы можете определить переименование исходного перечисления и передать его вспомогательной функции, чтобы получить более строго типизированное перечисление:
enum _Condition { NEW, USED };
const Condition = strongReverseEnumMapping(_Condition);
/* const Condition: {
readonly NEW: _Condition.NEW;
readonly USED: _Condition.USED;
} & {
readonly 0: "NEW";
readonly 1: "USED";
} */
type Condition = _Condition;
Теперь известно, что Condition
имеет "NEW"
на клавише 0
и "USED"
на клавише 1
, и ваша операция Condition[Condition.NEW]
работает так, как вы ожидаете:
const c = Condition[Condition.NEW]
// ^? const c: "NEW";
Детская площадка, ссылка на код
Это отличный отчет о том, как работают внутренности Typescript. Было очень приятно читать, и это действительно поучительно. Огромное вам спасибо за это! Я чувствую, что это лучший ответ на мой вопрос (он не меняет значения моего перечисления). Тем не менее, для моего варианта использования ответ Робби лучше подходит, поскольку полученный код гораздо более читабелен.
Что возвращает API? Числа или строки?