TypeScript: [Страна, Штат], где параметры автозаполнения штата меняются в зависимости от введенной страны

Вот мой пример кода:

const AUSTRIA = ['Vienna', 'Raiding'] as const;
const GERMANY = ['Berlin'] as const;
const testCountries = {
    Austria: AUSTRIA,
    Germany: GERMANY
} as const;

type CountriesType = typeof testCountries;
type CountryStateType<T extends CountriesType> = 
  [keyof T, T[keyof T][number]]; // error!
//          ~~~~~~~~~~~~~~~~~~
// Type 'number' cannot be used to index type 'T[keyof T]'.

const countryAndState: CountryStateType<CountriesType> = ['Austria', 'Raiding'];

Этот фрагмент кода ниже работает, и это ожидаемое поведение для countryAndState[1] ссылки на CountryStateType:

const StateBasedOnCountry: CountriesType['Austria'][number] = 'Raiding';

Почему возникает ошибка и как ее исправить?

Добро пожаловать в Stack Overflow! • Пожалуйста, отредактируйте , чтобы в вашем примере было как минимум два ключа, если вы собираетесь использовать keyof T. • Скриншоты IDE, к сожалению, здесь неуместны. Пожалуйста, замените изображения кода/журналов/ошибок/подсказок текстовыми версиями. • Думаю, вы ищете подход, показанный по этой ссылке на игровую площадку. Если да, то я мог бы написать ответ или найти подходящий источник дублирования. Если нет, то что мне не хватает?

jcalz 25.07.2024 04:34

Добрый день @jcalz. Ссылка, которую вы предоставили в ссылке на игровую площадку, действительно похожа на то же самое поведение, с которым я столкнулся.

nikitimi 25.07.2024 05:52

Боюсь, я вообще не понимаю ни вашего комментария, ни вашего редактирования, извините. Что вы подразумеваете под «дублированием»? Что значит «то же самое поведение, которое я испытывал»? Почему вы включили мое предложенное решение в вопрос? Пожалуйста, удалите мой код из вопроса, если он решил вашу проблему (решения не относятся к вопросам); Я напишу это как ответ. Если это не решило вашу проблему, пожалуйста, прокомментируйте, чтобы уточнить. Еще раз извините за коммуникационный барьер.

jcalz 25.07.2024 14:24

Извините, если я запутал это, это частично решает проблему, поэтому я прокомментировал «то же самое поведение, которое испытывал и я», потому что если я добавлю еще одну страну в testCountries, штат добавленной страны будет включен в список штатов. Автозаполнение TypeScript независимо от выбранной страны.

nikitimi 26.07.2024 01:54

Вот ссылка на игровая площадка)

nikitimi 26.07.2024 02:00

TypeScript жалуется на то, что значением является Berlin, но не удаляет его в предложении автозаполнения.

nikitimi 26.07.2024 02:02

Тип, который вам нужен: ["Austria", "Vienna" | "Raiding"] | ["Germany", "Berlin"] (пожалуйста, отредактируйте , упомянув в вопросе Германию/Берлин). Если поведение автозаполнения TS вам не нравится, я мало что могу с этим поделать; см. ms/TS#46457 . Существуют косвенные подходы, такие как вспомогательная функция, как показано здесь, но это выходит за рамки вашего исходного вопроса (вы отредактировали его так, чтобы он касался «автозаполнения», но это расширение объема). Как мне действовать здесь?

jcalz 26.07.2024 05:02

Фрагмента, который вы предоставили по ссылке игровая площадка, достаточно для моего вопроса. Однако использование вспомогательной функции может вызвать некоторые незначительные проблемы с производительностью (я не знаю), я приму ваш первый фрагмент в качестве ответа. Спасибо @jcalz!

nikitimi 26.07.2024 07:48
Зод: сила проверки и преобразования данных
Зод: сила проверки и преобразования данных
Сегодня я хочу познакомить вас с библиотекой 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 для повышения производительности приложения путем загрузки модулей только тогда, когда они...
1
8
65
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Ошибка возникает потому, что общее ограничениеT extends CountriesType не означает, что T в точности CountriesType; это может быть любой возможный подтип или расширение, добавляющее любые свойства:

interface Foo extends CountriesType {
    abc: boolean;
}
type Z = CountryStateType<Foo>; // allowed, oops

Как вы можете видеть, T[keyof T] может не иметь индексной подписи number (boolean определенно нет) и, следовательно, T[keyof T][number] недействителен.


Чтобы это исправить, я бы посоветовал вам ограничить T чем-то менее привязанным к CountriesType. Если вы хотите, чтобы T имел массив строк для каждого свойства, вы можете выразить это как T extends Record<keyof T, readonly string[]>:

type CountryStateType<T extends Record<keyof T, readonly string[]>> =
    [keyof T, T[keyof T][number]]; // okay, no error here anymore

type Z = CountryStateType<Foo>; // error
//                        ~~~ <-- Types of property 'abc' are incompatible.

Однако вы еще не закончили. Тип [keyof T, T[keyof T][number]] неправильно выражает корреляцию между каждым ключом T и соответствующими допустимыми строками:

type Y = CountryStateType<CountriesType>;
//  type Y = ["Austria" | "Germany", "Vienna" | "Raiding" | "Berlin"] 

Этот тип допускает перекрестные термины, такие как ["Austria", "Berlin"]. Если вы хотите это исправить, вам нужно распространить [K, T[K][number]] по союзу для K в keyof T. Один из способов сделать это — использовать тип распространяемого объекта, как описано в microsoft/TypeScript#47109:

type CountryStateType<T extends Record<keyof T, readonly string[]>> =
    { [K in keyof T]: [K, T[K][number]] }[keyof T]

type Y = CountryStateType<CountriesType>;
// type Y = ["Austria", "Vienna" | "Raiding"] | ["Germany", "Berlin"]

И это правильный тип. Это ответ на заданный вопрос (или, по крайней мере, на тот, который был задан изначально).


Конечно, вы обнаружите, что автопредложение/автозаполнение IntelliSense не работает так, как вы хотите. Вы по-прежнему будете получать перекрестные предложения (хотя, если вы примете такие предложения, как и ожидалось, возникнет ошибка). Это известная ошибка в TypeScript, о которой сообщается в microsoft/TypeScript#46457. Возможно, это будет исправлено в какой-нибудь следующей версии TypeScript. До тех пор вы можете обойти это, возможно, с помощью общей вспомогательной функции:

const countryStateType = <T extends Record<keyof T, readonly string[]>>() =>
    <K extends keyof T>(k: K, v: { [P in K]: T[P][number] }[K]): CountryStateType<T> => [k, v]

const another = countryStateType<CountriesType>()("Austria", "");
//                                                           ^^ <-- you get autocomplete here

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

Детская площадка, ссылка на код

Большое спасибо! Действительно ценю это!

nikitimi 29.07.2024 04:29

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

Похожие вопросы

После недопустимого входа на сервер ASP.NET MVC, как я могу записать текст ошибки в возвращенном ответе Json и отобразить его в клиенте Angular?
Angular – почему мой раскрывающийся список не работает
Angular V18 PM [vite] Внутренняя ошибка сервера: страница /home не отображается в течение 30 секунд
Как выполнить проверку Type Guard для индексированного свойства
Angular 18 @для возврата ОШИБКИ TypeError: newCollection[Symbol.iterator] не является функцией
Сохранение информации о типе при обеспечении связи типов между членами кортежа при использовании в параметре функции сопоставленного типа
Как упростить это троичное выражение?
Ошибка Angular 18: TS-992003: нет подходящего токена внедрения для параметра MovieService класса MovielistComponent
Как избежать .then или повторного ожидания при объединении обещаний в TypeScript?
Получен статус ответа [FAILED] от пользовательского ресурса. Возвращено сообщение: Команда умерла с <Signals.SIGKILL: 9>