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>