Почему типы динамических кортежей Typescript показывают все параметры в Autocompletion/Intellisense?

Я очень новичок в Typescript, и у меня есть проблема, которую я не знаю, как ее решить :(.

В основном я хочу создать список кортежей из списка компонентов. Первый элемент кортежа — это имя элемента (ключ MyComponents), а вторые элементы — его атрибуты.

(см. код ниже)

playground link

 interface MyComponents  {
    Container: {
      fluid?: boolean
      className?: string
    },
    Tag: {
      text?: string
      className?: string
      hidden: boolean
    }
}

//Get the keys of the list of my components
type Element = keyof MyComponents
 
//Get the attributes depending on the element name
type PickAttributes<T extends Element> = Pick<MyComponents[T], keyof MyComponents[T]>

//Create a mapped tuple type [Element, Attributes]
// and the attributes depend on the element
export type Tuple = { 

  [Element in keyof MyComponents] : [Element, PickAttributes<Element>]

}[keyof MyComponents]

 

const attr : PickAttributes<'Tag'> = {hidden : false} //This works and the auto completion works perfectly

const tuple1 : Tuple = ["Tag", { hidden: false}] //This also works

const tuple2 : Tuple = ["Container", { hidden: false}] //Error but it's normal as the element 'Container' doesn't have the property hidden:boolean

Все работает отлично, но есть небольшая проблема с автодополнением. Когда я набираю первый элемент (Контейнер, Тег,...), автодополнение второго элемента (его атрибутов) показывает все возможные атрибуты, даже неправильные.

Например, если я набираю «Тег» для первого элемента, он предлагает мне «жидкий», но «жидкий» доступен только в «Контейнере»! Intellisense показывает все варианты

И когда я выбираю жидкость, она также знает, что она несовместима...

Typescript знает, что он несовместим

Итак, мой вопрос: Как я могу ограничить автозаполнение, чтобы отображались только действительные атрибуты в зависимости от имени элемента?

Любая помощь будет оценена! Спасибо !**


person ddStack    schedule 23.04.2021    source источник
comment
настолько странно для меня, что вы должны цитировать имя типа тега. думал, что у меня все в порядке с машинописью, но я думаю, что нет. ха-ха   -  person djangofan    schedule 23.04.2021
comment
@djangofan keyof MyComponents эквивалентен фактическому строковому значению, потому что ключи объекта MyComponents сами являются строками. keyof MyComponents === "Tag" | "Container"   -  person jered    schedule 24.04.2021


Ответы (1)


TypeScript не знает, какой тип второго элемента в вашем кортеже основан на первом, он знает только, что они должны каким-то образом совпадать. Думайте об этом так: так, как вы написали свой тип Tuple, TypeScript не может выяснить, соответствуют ли оба элемента друг другу, пока вы не напишете оба из них. Даже если вы сначала наберете "Tag", это не даст TypeScript достаточно информации, чтобы сделать вывод о том, каким должен быть тип второго элемента. Но как только вы наберете оба элемента, TypeScript сможет, по крайней мере, сравнить их и проверить, совпадают ли они. Итак, никакого полезного intellisense/autocomplete.

Мы можем исправить это с помощью дженериков, но, к сожалению, они не очень просты при работе с кортежами. У вас есть два варианта:

#1 Сделайте тип Tuple универсальным

export type Tuple<T extends keyof MyComponents> = [T, MyComponents[T]]

Это делает две важные вещи: устанавливает ограничения на тип T и позволяет TypeScript выводить информацию о том, какую форму должен иметь кортеж, на основе T. По сути, вы сообщаете TypeScript: этот кортеж будет иметь T в первом элементе и MyComponents[T] во втором элементе, поэтому, если я передам вам "Tag" как T, вы точно знаете, что делать!

Недостатком является то, что TypeScript не может определить, что такое T, вам нужно явно передать его в универсальный тип, например:

const test1 : Tuple<"Tag"> = ["Tag", { hidden: false}];
const test2 : Tuple<"Container"> = ["Container", { h }];

Это вызывает некоторое дублирование, потому что вам нужно не только пройти, например. "Tag" в универсальный тип, вы также должны определить его как буквальное значение в своем кортеже. Тем не менее, вы ДЕЙСТВИТЕЛЬНО получаете улучшенное автозаполнение, которое вам нужно; после того, как вы написали Tuple<"Tag">, запись остальной части кортежа будет разумно знать, какие существуют возможные значения, и соответственно ограничивать их.

# 2 (лучше) Используйте универсальные функции

Универсальные функции намного мощнее и могут выводить типы, даже если они не определены явно. Аргументы универсального типа и аргументы функции очень похожи, поэтому TypeScript может по существу комбинировать их и использовать аргументы функции как способ прямого вывода аргументов универсального типа. Наблюдать:

function makeTuple<T extends keyof MyComponents>(key: T, props: MyComponents[T]) {
  return [key, props];
}

const test1 = makeTuple("Tag", { hidden: true });
const test2 = makeTuple("Container", { fluid: true });

Это довольно приятно, потому что вы не только получаете ту же способность автозаполнения/разума, что и в методе №1, но и вам не нужно дублировать себя при написании кода. Он немного меняет способ инициализации значения кортежа, но по-прежнему довольно прост и удобен в использовании.

Игровая площадка

person jered    schedule 24.04.2021