'Vue 3 - Extend a Vue component with additional props with Typescript support

I'm trying to extend a 3rd party Vue component (from PrimeVue) and add additional props, but I'd like to keep the typings of the original component and also union them with my additional props.

For example, using the 3rd party Button component below which has typings that enforce "label" must be a string, my IDE (VSCode + Vetur) will mark it as an error if I attempt to pass a numeric.

If I were to extend the Button class into my own component, BaseButton, using the common pattern below, I will be able to pass a numeric or any other type to the label prop without the IDE complaining. Besides maintaining the typings of the original Button component, I want to union it with my additional props, e.g. myProp: string below.

<template>
  <Button v-bind="$attrs" :label="myComputedLabel">
    <template v-for="(_, slot) of $slots" v-slot:[slot]="scope">
      <slot :name="slot" v-bind="scope"/>
    </template>
  </Button>
</template>

<script>
  export default defineComponent({
  name: 'BaseButton',
  props: {
    myProp: {
      type: String
    }
  },
  setup(props) {
    const myComputedLabel = computed(() => {
      return `Hello ${props.myProp}`;
    });
  }
 })
</script>

In my case, the 3rd party component is written in Vue with vanilla JS but has an external Button.d.ts typings file. I'm not sure if that matters.



Solution 1:[1]

you can read it from the component's setup option, the props is the first argument of setup

type MyButtonProp = Parameters<NonNullable<typeof Button.setup>>[0]

Simply, here's a Generic Type

type ComponentProps<T extends { setup?: any }> = Parameters<NonNullable<T['setup']>>[0]

usage

type MyButtonProp = ComponentProps<typeof Button>

Solution 2:[2]

If you're using Vue 3 + script setup, you can do it like this

import Button from 'primevue/button';

type ButtonInstance = InstanceType<typeof Button>

defineProps<{
  label: ButtonInstance['$props']['label'] | number
}>()

For setup function

import { PropType } from 'vue'

defineComponent({
  props: {
    label: {
      type: [String, Number] as PropType<ButtonInstance['$props']['label'] | number>,
    }
  },
})

Solution 3:[3]

You could use Typescript + Setup

<script setup lang="ts">
import { computed } from "vue"
let props = defineProps<{ myProp: string | number }>()
const myComputedLabel = computed(() => {
   return `Hello ${props.myProp}`;
})
</script>

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 wobsoriano
Solution 3 bill.gates