Build Type-Safe Components with Vue 3.3
Tags: vue, typescript, javascript
Published: 2023-05-16 22:03:27

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](https://blog.vuejs.org/posts/vue-3-3):

```javascript
<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: 
```bash
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:

```javascript
<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.


![Inline type hinting](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jqfasxjx90zl63cvogf1.png)

---

#### 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.

![Not required type](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zklo7ngu7bojr33707kn.png)

---

#### 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.**

![Import type from OptionProps](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6fmycug6rynnjxkgyer1.png)

---

#### 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. 

![Wrong type modelValue](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qsv4z5vvagivdsymq93i.png)

---

## 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!**  ❤️ 💻