Использование `keyof typeof enum` в качестве параметра функции

Я столкнулся с проблемой, которую не могу решить в 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? Числа или строки?

Robby Cornelissen 03.09.2024 11:49

Предположительно, API не может возвращать значения перечисления TypeScript, он возвращает что-то еще, что вы хотите выразить как тип перечисления TypeScript. enum condition {NEW = 'NEW', USED = 'USER'}; работает для вашего случая, если он действительно возвращает строки набора значений. Также вы можете просто сказать type condition = 'NEW'|'USED', чтобы указать, что он возвращает строку, которая может быть любым из этих двух значений.

apokryfos 03.09.2024 11:57

Это ms/TS#38806 и ms/TS#50933. Это недостающая функция, и вам придется обойти ее, например, выполнив утверждение типа, которое вы делаете (при условии, что вы действительно хотите использовать обратные сопоставления и не хотите полностью избегать таких перечислений). Это полностью решает вопрос? Если да, то я напишу ответ с объяснением; если нет, то что мне не хватает?

jcalz 03.09.2024 16:27

@jcalz Похоже, это действительно ошибка, а не то, что я делаю неправильно. Думаю, в этом случае использование сопоставлений строк имеет смысл.

César 04.09.2024 11:40
Зод: сила проверки и преобразования данных
Зод: сила проверки и преобразования данных
Сегодня я хочу познакомить вас с библиотекой Zod и раскрыть некоторые ее особенности, например, возможности валидации и трансформации данных, а также...
Как заставить Remix работать с Mantine и Cloudflare Pages/Workers
Как заставить Remix работать с Mantine и Cloudflare Pages/Workers
Мне нравится библиотека Mantine Component , но заставить ее работать без проблем с Remix бывает непросто.
Угловой продивер
Угловой продивер
Оригинал этой статьи на турецком языке. ChatGPT используется только для перевода на английский язык.
TypeScript против JavaScript
TypeScript против JavaScript
TypeScript vs JavaScript - в чем различия и какой из них выбрать?
Синхронизация localStorage в масштабах всего приложения с помощью пользовательского реактивного хука useLocalStorage
Синхронизация localStorage в масштабах всего приложения с помощью пользовательского реактивного хука useLocalStorage
Не все нужно хранить на стороне сервера. Иногда все, что вам нужно, это постоянное хранилище на стороне клиента для хранения уникальных для клиента...
Что такое ленивая загрузка в Angular и как ее применять
Что такое ленивая загрузка в Angular и как ее применять
Ленивая загрузка - это техника, используемая в Angular для повышения производительности приложения путем загрузки модулей только тогда, когда они...
2
4
51
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Предполагая, что 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. Было очень приятно читать, и это действительно поучительно. Огромное вам спасибо за это! Я чувствую, что это лучший ответ на мой вопрос (он не меняет значения моего перечисления). Тем не менее, для моего варианта использования ответ Робби лучше подходит, поскольку полученный код гораздо более читабелен.

César 05.09.2024 09:27

Другие вопросы по теме

Почему сопоставление массива не работает с той же записью сопоставления рекламы?
Почему машинописный текст не позволяет частично указывать аргументы типа, чтобы создать новую универсальную функцию из другой универсальной функции?
Компонент React со свойством «as», которое указывает на другой компонент и также наследует его свойства
Как называется событие, которое передает только аргументы типа в общую функцию в машинописном тексте?
Инвертировать ключ типа T
Машинописный текст говорит, что собственность не существует
Как заставить TypeScript возвращать другое значение в зависимости от необязательного аргумента
Использование дженериков для сопоставления одного строкового литерала в Typescript
Что означает `T расширяет только чтение неизвестно [] | []` в сигнатуре функции TypeScript `Promise.all`?
Репликация агрегации проекта mongodb в Typescript