3

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.

1

4 Answers 4

4

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>,
    }
  },
})

Another way of extracting types of prop

import Button from 'primevue/button'

type ExtractComponentProps<TComponent> =
  TComponent extends new () => {
    $props: infer P;
  }
    ? P
    : never

type ButtonTypes = ExtractComponentProps<typeof Button>
Sign up to request clarification or add additional context in comments.

Comments

1

Based on my experience there are multiple ways to achieve this depending on the type complexity and Vue version in use.

script setup

With the introduction of defineProps it became less tedious to define prop types (i.e. not relying on casting to PropType<T> and less verbose prop definition). Vue 3.3+ added support for fairly complex types and imported types also with few limitations.

Here's an example of ComponentB extending ComponentA.

<script setup lang="ts">
import ComponentA from 'componentA.vue'

interface AdditionalProps {
  // ... define your props here
}

type Props = AdditionalProps & (typeof ComponentA)['$props']

const props = defineProps<Props>()

//You can also use `withDefaults` to define default values

</script>

<template>
  <ComponentA v-bind="props">
</template>

You can even benefit from utility types like Omit to remove some Props from the extended component.

Unfortunately, this does not always work because as mentioned defineProps cannot handle complex types like "conditional types". I couldn't get it to work with Vuetify.

defineComponent

defineComponent's first generic parameter is Props type.

<script lang="ts">
import ComponentA from 'componentA.vue'

interface AdditionalProps {
  // ... define your props here
}

type Props = AdditionalProps & (typeof ComponentA)['$props']

const ComponentB = defineComponent<Props>({
  props: {
    ...(ComponentA.props as any) //This line ensures that "additional" props are not handled as "attrs"
  }
})
</script>

<template>
  <ComponentA v-bind="props">
</template>

NOTE ON SomeComponent[$props]

While it is not publicly documented editor tools rely on this for auto-complete, as far as I can understand.

You might want to define a util type to extract any component props like this:

type ComponentProps<T extends {$props: any}> = Omit<T['$props'], `$${string}` | `v-slot${string}`>

The reason why I Omit the specified string patterns is that these are internal properties and might cause Typescript to become laggy while editing or reporting errors.

Extending/Typing slots

Again you could make use of script setup's defineSlots introduced in Vue 3.3. Or rely on a hack that was used in old versions of vue-router itself if you want to use defineComponent

Example of using defineSlots grabbed from the blog post

<script setup lang="ts">
defineSlots<{
  default?: (props: { msg: string }) => any
  item?: (props: { id: number }) => any
}>()
</script>

Using defineComponent (credit goes to Abdelrahman Awad's Blog post)

<script lang="ts">
import ComponentA from 'componentA.vue'
    
const ComponentB = defineComponent({
  //... component definition
})
    
const AugmentedComponentB = ComponentB as typeof ComponentB & {
  new(): {
     $slots: (typeof ComponentA)['$slots']
   }
}
export default AugmentedComponentB
</script>

Comments

0

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>

Comments

0

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>

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.