Generic Component Types does not work with InstanceType<typeof component>
Vue version
3.3.4
Link to minimal reproduction
https://stackblitz.com/edit/vitejs-vite-tpwnzy?file=src/App.vue
Steps to reproduce
Run "turbo typecheck" inside StackBlitz.
What is expected?
Either
ref<InstanceType<typeof WithGeneric<number>>>()
or
ref<InstanceType<typeof WithGeneric>>()
should generate correct typings.
What is actually happening?
src/App.vue:6:28 - error TS2344: Type 'typeof WithGeneric<number>' does not satisfy the constraint 'abstract new (...args: any) => any'.
Type 'typeof WithGeneric<number>' provides no match for the signature 'new (...args: any): any'.
6 const a = ref<InstanceType<typeof WithGeneric<number>>>(); // type error
~~~~~~~~~~~~~~~~~~~~~~~~~~
src/App.vue:7:28 - error TS2344: Type '<T>(__VLS_props: Options<T> & VNodeProps & AllowedComponentProps & ComponentCustomProps, __VLS_ctx?: Pick<{ props: Options<...>; expose(exposed: { ...; }): void; attrs: any; slots: {}; emit: any; }, "attrs" | ... 1 more ... | "slots"> | undefined, __VLS_setup?: { props: Options<...>; expose(exposed: { ...; }): void;...' does not satisfy the constraint 'abstract new (...args: any) => any'.
Type '<T>(__VLS_props: Options<T> & VNodeProps & AllowedComponentProps & ComponentCustomProps, __VLS_ctx?: Pick<{ props: Options<...>; expose(exposed: { ...; }): void; attrs: any; slots: {}; emit: any; }, "attrs" | ... 1 more ... | "slots"> | undefined, __VLS_setup?: { ...; }) => VNode<...> & { ...; }' provides no match for the signature 'new (...args: any): any'.
7 const b = ref<InstanceType<typeof WithGeneric>>(); // type error
~~~~~~~~~~~~~~~~~~
System Info
No response
Any additional comments?
No response
Not directly at least, that's true. Most probably the typing is broken independant on how you define them.
Facing the same issue. Cant type my generic component with
const contentSwiperRef = ref<InstanceType<typeof ContentSwiper> | null>(null);
Hey @ml1nk Can you try using ReturnType
instead of InstanceType
Reference: https://github.com/vuejs/language-tools/issues/3206#issuecomment-1563399071
Just found out about this issues and maybe I got some workaround for now (feedback to verify welcome 🤞)
For my use case this seems to work with props
and defineExpose()
So for usage I landed on:
type TestType = { id: number; thing: string }
type MyGenericComponentType = PseudoComponent<typeof MyGenericComponent<TestType>>;
const myGenericComponentRef = ref<InstanceType<MyGenericComponentType>>();
and here's the definition until there is a fix (feel free to improve and adapt, it definitely needs some love)
import {
DefineComponent,
RendererElement,
RendererNode,
VNode,
VNodeProps,
} from "vue";
type NoUndefined<T> = T extends undefined ? never : T;
type Props =
| (VNodeProps & {
[key: string]: any;
})
| null;
// maybe this can be typed as `SetupContext`
export type Context = {
props: any;
attrs: any;
slots: any;
emit: any;
expose: (exposed?: any) => void;
};
export type ComponentReturn = VNode<RendererNode, RendererElement, Props> & {
__ctx?: Context;
};
export type PseudoComponent<
T extends (...args: any[]) => ComponentReturn,
PseudoReturnType extends ComponentReturn = ReturnType<T>,
PseudoContext extends ComponentReturn["__ctx"] = PseudoReturnType["__ctx"],
PseudoProps = NoUndefined<PseudoContext>["props"],
PseudoExposed = Parameters<NoUndefined<PseudoContext>["expose"]>[0]
> = DefineComponent<PseudoProps, PseudoExposed>;
Here's a plot twist:
The actual shape of type T defined by the generic component is known when this component's genericaly-typed prop is being assigned.
<template>
<div><slot :item="item" /></div>
</template>
<script lang="ts" setup generic="T = any">
defineProps<{
item: T
}>()
defineSlots<{
default(props: { item: T }): any
}>()
defineExpose({
sayHello(item: T) { console.log('Hello!', item) }
})
</script>
Assuming that happens inline, like so:
<template>
<Example ref="example" :item="{ x: 1 }" v-slot="{ item }">
{{ item }}
</Example>
</template>
<script lang="ts" setup>
import { ref, onMounted } from 'vue'
import Example from './Example.vue'
const example = ref<InstanceType<typeof Example>>()
onMounted(() => {
example.value?.sayHello({ x: 2 })
})
</script>
I would expect that to work, but the types are totally wrong from that Example
import of generic component.
The twist that I am referring to is in the binding to :item
- that defines the shape of data of this particular instance of the Example
component. So, if a ref
is bound to a specific instance, we can immediately tell what the actual type is - by looking at what has been provided as the binding. And so the sayHello
function can be fully typed - not relying on additional types provided to the ref<InstanceType<typeof Example<HERE>>()
.
If you try to inspect the content of that item
in default slot it shows that item
indeed has an x
of type number
:
Does that make sense?
Please track for https://github.com/vuejs/language-tools/issues/3206