Вот мой пример кода:
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';
Почему возникает ошибка и как ее исправить?
Добрый день @jcalz. Ссылка, которую вы предоставили в ссылке на игровую площадку, действительно похожа на то же самое поведение, с которым я столкнулся.
Боюсь, я вообще не понимаю ни вашего комментария, ни вашего редактирования, извините. Что вы подразумеваете под «дублированием»? Что значит «то же самое поведение, которое я испытывал»? Почему вы включили мое предложенное решение в вопрос? Пожалуйста, удалите мой код из вопроса, если он решил вашу проблему (решения не относятся к вопросам); Я напишу это как ответ. Если это не решило вашу проблему, пожалуйста, прокомментируйте, чтобы уточнить. Еще раз извините за коммуникационный барьер.
Извините, если я запутал это, это частично решает проблему, поэтому я прокомментировал «то же самое поведение, которое испытывал и я», потому что если я добавлю еще одну страну в testCountries, штат добавленной страны будет включен в список штатов. Автозаполнение TypeScript независимо от выбранной страны.
Вот ссылка на игровая площадка)
TypeScript жалуется на то, что значением является Berlin, но не удаляет его в предложении автозаполнения.
Тип, который вам нужен: ["Austria", "Vienna" | "Raiding"] | ["Germany", "Berlin"] (пожалуйста, отредактируйте , упомянув в вопросе Германию/Берлин). Если поведение автозаполнения TS вам не нравится, я мало что могу с этим поделать; см. ms/TS#46457 . Существуют косвенные подходы, такие как вспомогательная функция, как показано здесь, но это выходит за рамки вашего исходного вопроса (вы отредактировали его так, чтобы он касался «автозаполнения», но это расширение объема). Как мне действовать здесь?
Фрагмента, который вы предоставили по ссылке игровая площадка, достаточно для моего вопроса. Однако использование вспомогательной функции может вызвать некоторые незначительные проблемы с производительностью (я не знаю), я приму ваш первый фрагмент в качестве ответа. Спасибо @jcalz!






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