How to type a Generic component in Vue 3's <script setup>?
Asked Answered
H

3

27

I have a Select component that accepts an array of options. Each option can be an object of anything as long as it has the following attributes id and text

So I typed it like this:

type SelectOption<T> = {
  id: string | number
  text: string
} & T

But I'm not sure how to use this with defineProps and also defineEmits in the component.

defineProps<{
  options: SelectOption<??>
  modelValue: SelectOption<??>
}>()

defineEmits<{
  (event: 'update:modelValue', SelectOption<??>): void
}>()
Hospitalization answered 31/12, 2021 at 13:3 Comment(2)
For anyone stumbling upon this, here's some related reading: github.com/vuejs/core/issues/3102 logaretm.com/blog/generically-typed-vue-components Long story short, it's not really possible right now.Slather
For anyone stumbling upon it 1 year later, it can be done now. See this answer below: https://mcmap.net/q/519716/-how-to-type-a-generic-component-in-vue-3-39-s-lt-script-setup-gtOrientalize
C
33

Here is an updated answer to account for the recent improvements in this area :

This allows you to write the following code (from the linked release blog post) :

<script setup lang="ts" generic="T extends string | number, U extends Item">
import type { Item } from './types'
defineProps<{
  id: T
  list: U[]
}>()
</script>

Of course, this requires you to upgrade to the latest versions of vue and volar.

Note that as of writing this (May 2023), this feature is a bit rough around the edges but is still actively developed.

Chifforobe answered 12/5, 2023 at 5:11 Comment(3)
My code looks almost exactly like your example, but I get this error: Property 'items' of exported interface has or is using private name 'T'Latter
I figured out that this happens if you declare the types of your props using an interface. The prop types must be literal.Latter
Note that, as of Vue 3.3.4, generic components are still very buggy, unfortunately. Volar extension is also pretty bad at handling them.Coakley
M
4

The approach I took worked in VS Code + Volar 0.40.13 and it also works with Volar 1.0.0. (ensure you update your tsconfig.json file to include "vueCompilerOptions.jsxTemplates": true)

You first define the "non-generic" Vue component using a "non-generic" version of your type.

// MySelectBase.vue

type SelectOptionBase = {
  id: string | number;
  text: string;
  [key: string]: unknown;
}

defineProps<{
  options: SelectOptionBase[];
  modelValue: SelectOptionBase;
}>()

defineEmits<{
  (e: 'update:modelValue', v: SelectOptionBase): void
}>()

Then define the "Generic" version of the component.

<script lang="ts">
import MySelectBase from "./MySelectBase.vue";

export type SelectOption<T = {}> = {
  id: string | number
  text: string
} & T;

interface Props<T> {
  options: SelectOption<T>[];
  modelValue: SelectOption<T>;
}

type MySelect = new <T = {}>(props: Props<T>) => {
  $props: Props<T>;
  $emit: {
    (e: "update:modelValue", v: SelectOption<T>): void;
  };
};

export default MySelectBase as MySelect;
</script>

You can then use the MySelect component

<script setup lang="ts">
import { ref } from "vue";
import MySelect from "./MySelect.vue";

const item = ref<SelectOption<{ name: string; }>>();
const items = ref<SelectOption<{ name: string; }[]>>([
  { id: 1, text: "Johnny", name: "John" },
  { id: 2, text: "MJ", name: "Mary-Jane" },
]);
</script>

<template>
  <MySelect v-model="item" :options="items" />
</template>

When you hover over the options prop it should show MySelect<{...}>.options: { id: number, text: string; name: string }

Modulus answered 8/10, 2022 at 6:40 Comment(1)
While this answer has been incredibly useful in the past, generically typed components are now officially supported (see my answer below)Chifforobe
G
0

Use a default type value, so it doesn't have to be specified:

type SelectOption<T = Record<string, unknown>> = {
  id: string | number
  text: string
} & T
defineProps<{
  options: SelectOption[]
  modelValue: SelectOption
}>()

defineEmits<{
  (event: 'update:modelValue', value: SelectOption): void
}>()

In the end, your component should not know anything else about your options, except they have id and text.

Guss answered 16/2, 2023 at 12:59 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.