Five days ago, Vue version 3.3 "Rurouni Kenshi" was officially released and it's packed with amazing features enhancing DX and working with Typescript. In this post we are going to look at some of the cool new features and how the new Generic Types feature can help you to build better type-safe components.
What are Generic Types?
You can use Generic Types to check whether two or more props passed to a component do actually have the same type. This could be required when for example building a Select component, as shown in the following example from the official blog post from Evan You:
<script setup lang="ts" generic="T">
defineProps<{
items: T[]
selected: T
}>()
</script>
This means, when supplying an input for the items property of type array<string>
you would have to supply a value of type string
or else Typescript would yell at you.
But when do I actually need this?!
The example above might feel a bit abstract, so let's look at a "real world" scenario where this could actually come in handy.
We are assuming an application, where we are fetching data with different models from the server as for example users and roles. We might wanna use a Select component that works with both types of data. Usually I would also add a Headless component library like Headless UI.
Generic Types in Action
To set up a quick demo we run:
npx create-vite@latest vue-demo --template vue-ts
cd vue-demo
npm install
To set up Headless UI: npm install @headlessui/vue
The Select component
We can create a select component that use a Generic Type T that extends an object of type OptionProps = {id: string | number, name: string}
, as follows:
<script setup lang="ts" generic="T extends OptionProps">
import { Listbox, ListboxButton, ListboxOptions, ListboxOption, ListboxLabel, } from "@headlessui/vue";
import { computed } from "vue";
// define and export Option props
export type OptionProps = {
id: string | number;
name: string;
};
// define props by using generic
const props = defineProps<{
options: T[];
modelValue: T;
}>();
// define emits
const emit = defineEmits<{
"update:modelValue": [value: T];
}>();
// define proxyValue for modelValue to emit update:modelValue
const proxy = computed({
get() {
return props.modelValue;
},
set(value: T) {
emit("update:modelValue", value);
},
});
</script>
<template>
<Listbox as="div" v-model="proxy">
<ListboxLabel> Select an option </ListboxLabel>
<ListboxButton> {{ proxy.name }} ▼ </ListboxButton>
<ListboxOptions>
<ListboxOption v-for="option in options" :key="option.id" :value="option">
{{ option.name }}
</ListboxOption>
</ListboxOptions>
</Listbox>
</template>
Now that we have defined our <MySelect />
component we can start using it in other places. This is where the power of typescript comes into play.
1. We have the awesome inline-type hinting by Volar extension
This is clearly one of my most favorite features of using Vue with Typscript. When some props on a component are required, Volar will inline hint them that they need to be included.
2. We will get notified when we our input does not satisfy the required prop type
By using the generic T, and defining the required props we know when we are missing some props on the supplied input.
3. We can import OptionProps
from the component
To make sure that we supply the correct type to the component, we can define an interface User
that does extend our required type OptionProps
from <MySelect />
. This feature of importing types has also just been added recently.
4. We can make sure that our modelValue actually matches the input
As the modelValue requires an initial value of the type supplied with the options prop, it complains as the property supplied in this case is not valid.
That's a wrap!
I really think that Vue 3.3 is a huge boost for the overall DX when using Typescript and building type-safe components. For me personally this will be a huge boost in productivity and I am looking forward to integrate this in my future projects.
What do you think? Are you actually using Typescript with Vue. Have you been using any of the new features yet? And do you think you will find so place where you want to apply Generics to strongly type your components?
In case you liked this article, I would be happy if you'd left a like or to hear from you.
Thanks for reading and happy hacking! ❤️ 💻
Top comments (2)
I don't really understand how to use it! I've update vue and tried to write a gereic component, but TS always thor errors!
Furthermore how to pin the type:
In my another component I want to use not a generic component, like
Array<T>
, but specific oneArray<{ timeSecond: number; report: string; }>
so I want to distinguish 2 components:Hi Greg,
Sorry I do only answer to this now - I haven't been very active on here lately.
I hope you have found a solution now already.
I think for TS to recognize the type of the props you need to use the following notation
const props = defineProps<{modelValue?: T}>()
.As for the other problem you have mentioned I think you would do something with extending your generic type like:
Array<T & {report: string}>