My app uses Vue 3.3.4 which added support for generics in single file components. My component is a generic list that loops over a set of items
passed as a prop. Additionally, there is an itemKey
prop for keying the v-for
loop.
<script setup lang="ts" generic="T, K extends keyof T">
defineProps<{
items: Array<T>
itemKey: K
}>()
</script>
<template>
<ul>
<li v-for="item in items" :key="item[itemKey]">
<slot :item="item" />
</li>
</ul>
</template>
The :key="item[itemKey]"
gets highlighted and the following error is shown
Type '[{ type: PropType<K>; required: true; }] extends [Prop<infer V, infer D>] ? unknown extends V ? IfAny<V, V, D> : V : { type: PropType<K>; required: true; }' cannot be used to index type 'T'.
It should be noted that this is only a type error; the code itself runs.
Why is my itemKey
being misinterpreted?
TEMPORARY SOLUTION: Use an inline //@ts-ignore
in the :key
prop.
<li
v-for="(item, i) in items"
:key="
//@ts-ignore
item[itemKey]
"
>
This is because the type of itemKey
is generic in the component definition and is only known at runtime. Therefore, the static type checking of TypeScript cannot determine exactly which key it refers to.
It is necessary to provide a more specific type definition for items[]
.
In Vue, the :key
attribute expects string
, number
, or symbol
values. Therefore, if the type of item[itemKey]
matches one of these types, we use it as the key otherwise, we don't.
T extends U ? U : never
- TypeScript 2.8 and aboveIn this case, we assume that every value in the object T
can be a valid key. After all, by using item[itemKey]
, we essentially made this assertion, which is causing the error.
Array<{ [key in keyof T]: T[key] extends string | number | symbol ? T[key] : never }>
The items
prop is an array where, based on the keys (keyof T
) of the T
type, it selects the types from T[key]
that are string
, number
, or symbol
. If the types are not one of these, it returns the never
type.
This ensures that only string
, number
, or symbol
values can be assigned to the :key
attribute.
<script setup lang="ts" generic="T, K extends keyof T">
defineProps<{
items: Array<{ [key in keyof T]: T[key] extends string | number | symbol ? T[key] : never }>
itemKey: K
}>()
</script>
<template>
<ul>
<li v-for="item in items" :key="item[itemKey]">
<slot :item="item" />
</li>
</ul>
</template>
item[itemKey]
type)If the object can contain values of different types, it may not be worth the effort to perform precise type checking. It is advisable to declare `item[itemKey] as a value that meets our expectations for later use. Additionally, you may consider writing runtime JavaScript checks to verify the type of item[itemKey], allowing you to display an error message in the console as an error during development mode.
<script setup lang="ts" generic="T, K extends keyof T">
defineProps<{
items: Array<T>
itemKey: K
}>()
</script>
<template>
<ul>
<li v-for="item in items" :key="(item[itemKey as K] as string | number | symbol)">
<slot :item="item" />
</li>
</ul>
</template>
If you want to ensure that the :key
attribute receives a string value, you can simply convert the item[itemKey]
value to a string. By doing so, TypeScript won't raise any issues because regardless of the actual type of item[itemKey]
, you have explicitly converted it to a string, which satisfies the requirements of the :key
attribute.
<script setup lang="ts" generic="T, K extends keyof T">
defineProps<{
items: Array<T>
itemKey: K
}>()
</script>
<template>
<ul>
<li v-for="item in items" :key="String(item[itemKey as K])">
<slot :item="item" />
</li>
</ul>
</template>