From my personal experience, there are various approaches to achieving this depending on the complexity of the type and the version of Vue being used.
script setup
With the introduction of defineProps
, it has become easier to define prop types without the need for casting to PropType<T>
and with less verbose definitions. In Vue 3.3+, support was added for complex types and imported types with some limitations.
Here's an example of extending ComponentB from 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>
Additionally, utility types like Omit
can be utilized to remove certain props from the extended component.
However, there are instances where this approach may not work as expected, especially when dealing with "conditional types". I faced challenges implementing this with Vuetify.
defineComponent
The first generic parameter of defineComponent
is the 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
]
Although not publicly documented, editor tools rely on this for auto-complete functionalities.
To extract any component props, a utility type can be defined as follows:
type ComponentProps<T extends {$props: any}> = Omit<T['$props'], `$${string}` | `v-slot${string}`>
The reason for using Omit
on specified string patterns is to prevent Typescript from becoming slow during editing or error reporting due to internal properties.
Extending/Typing slots
In addition, you can leverage defineSlots
from script setup
, introduced in Vue 3.3, or employ a workaround similar to what was used in older versions of vue-router
.
An example of using defineSlots
taken 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>