I've got a generic component receiving
props: {
options: {
type: Array as PropType<unknown[]>,
default: () => []
},
labelKey: {
type: String,
default: "label"
}
}
and, somewhere in the template I use
<SomeComponent
v-for="(item, index) in options"
v-text="item[labelKey]"
...
/>
Ideally I'd like to be able to pass the type of an option item to the component instead of unknown
(e.g: T
), and to let TS know labelKey
is keyof T
. Is this even possible in Vue?
Now my only option is to expect the error:
<SomeComponent
v-for="(item, index) in options"
/* @ts-expect-error: for TS item is unknown */
v-text="item[labelKey]"
...
/>
After spending some time on this, here's what I found 1, 2:
In Vue2, one could create a generic function returning a component definition:
const createGenericSearch<T>() {
return defineComponent({
props: {
options: {
type: Array as PropType<T[]>,
default: () => []
},
labelKey: {
type: String as PropType<keyof T>,
default: 'label'
}
},
// ...
})
}
...and use it in parent component as:
import { createGenericSearch } './path/to/it'
export default defineComponent({
component: {
MyLocalSearch: createGenericSearch<MyLocalItemType>()
}
})
Although this works, it has a pretty big disadvantage: you no longer use SFC for <GenericSearch />
which means you need to pass the template
as a prop of defineComponent
and move all the contents of its <style />
tag to a parent component.
But, by far, the biggest disadvantage of throwing away the SFC pattern is that Vue now has to be able to compile the template at runtime, so you'll need to use the Vue version including the compiler, which is bigger by more than 200k
. I'm all for using Typescript, but having a significantly bigger app size doesn't outweigh TS not knowing labelKey
is keyof OptionType
in my generic typed search component.
I should mention using generic types is also possible using Class API, but I won't detail it here as that API is deprecated.
In Vue3, the use of generic types has been made easy with
<script setup lang="ts" generic="T">
defineProps<{
options: T[]
labelKey: keyof T
}>()
</scipt>
, added in 3.3
.
See official documentation and this answer.
1 - Although I tagged this with vue3.js, the project is currently on vue@2.7
, getting prepped for upgrade to vue@3
. I'm currently transitioning all components from Options API to Composition and doing minor typescript improvements. When I asked this question I didn't know if generics were possible at all in Vue and that the syntax was different in ^2
and ^3
.
2 - I found this article quite useful in my research for creating a generic typed component