script-setup use `defineExpose`, component should have typescript declaration
What problem does this feature solve?
when my vue component 'MyComponent' use eg: defineExpose({ a: 1 })
, component instance can use instanceRef.value.a
, but component typescript declaration not have field a
when via InstanceType<typeof MyComponent>
What does the proposed API look like?
nothings
I have same problem, when run vue-tsc
will show error
error TS2339: Property 'getFormValue' does not exist on type '{ $: ComponentInternalInstance; $data: {}; $props: Partial<{}> & Omit<Readonly<{ page?: unknown; forms?: unknown; } & {} & { page?: PageService | undefined; forms?: TableFormConfig[] | undefined; }> & VNodeProps & AllowedComponentProps & ComponentCustomProps, never>; ... 10 more ...; $watch(source: string | Function...'.
but i don't konw how to fixed it
The problem still exists...
// HighFiveOverlay.vue
defineExpose({
onPlay,
})
const highFiveOverlayRef = ref<InstanceType<typeof HighFiveOverlay> | null>(null)
if (highFiveOverlayRef.value) {
highFiveOverlayRef.value.onPlay()
}
Property 'onPlay' does not exist on type '{ $: ComponentInternalInstance; $data...
Having the same issue.Only checking by as any.
// login-panel.vue
import LoginAccount from './login-account.vue';
const accountRef = ref<InstanceType<typeof LoginAccount>>()
const handleLoginClick = () => {
(accountRef.value as any)?.loginAction()
}`
// login-account.vue
const loginAction = () => {
console.log('login')
}
defineExpose({
loginAction,
})
We should narrow the problem to this code, because defineExpose
is syntactic sugar of it.
defineComponent({
setup(_, { expose }) {
expose({ a: 1 })
}
})
IMO expose
should not affect component type declaration, this is an API design issue rather than an implementation issue.
Any updates on this one? This would genuinely improve the developer experience. This issue is also the fourth most upvoted open issue in this repo.
In case this is useful for anyone, here's my current workaround:
MyComponent.d.ts
export interface IMyComponent {
doSomething(): void;
}
MyComponent.vue
<script setup lang="ts">
import type { IMyComponent } from './MyComponent';
defineExpose<IMyComponent>({
doSomething() {
// ...
},
});
</script>
I don't use the I
prefix in all interfaces, but in this case I think it's useful to distinguish from the component itself. You could also call it MyComponentPublicAPI
or anything else.
As per where to place the interface declaration, as far as I understand it's not possible to add it in the .vue
file itself, because these files are all resolved with a generic shim. So, even if you prefer to import it from ./MyComponent.vue
, you'll have to declare it in a declaration file anyways, so I don't think that's worth the trouble.
But this is meant to be a temporary workaround, is that right?
Ideally the language server should be able to detect the type of the argument passed to defineExpose
and add it to the component type, without the developer having to extract, declare, and export+import a separate interface
It is a workaround, but I'm not sure if it's temporary or the only viable solution with the current architecture. As far as I understand, .vue
files are declared with a generic shim, so you're already getting the same type for all components. I'm not sure if the language server can solve that, but if it could then I don't think we'd need to include the shim and it must be there for a reason.
// HelloWorld.vue
const helloWorld = () => {
window.alert('Hello World')
}
defineExpose({ helloWorld })
// App.vue
const hello = ref<ComponentPublicInstance<typeof HelloWorld & { helloWorld: () => void }>>()
onMounted(() => {
hello.value?.helloWorld()
})
It can track IDE autocomplete and work was fine. But I think it's very inconvenient to define the type like this way 😟
// App.vue
const hello = ref<{ helloWorld: () => void }>()
onMounted(() => {
hello.value?.helloWorld()
})
If you wanna use methods or member of component, just enter type of specific methods or member in Ref. It also work fine 🙂
My workaround:
// file: vue-type-helpers.ts
import { UnwrapRef } from '@vue/composition-api';
// or
import { UnwrapRef } from 'vue';
export type ExposedToComputed<T extends object> = {
[key in keyof T]: () => UnwrapRef<T[key]>;
};
<script lang="ts">
import type { ExposedToComputed } from './vue-type-helpers';
export default {
computed: {} as ExposedToComputed<typeof exposed>,
};
</script>
<script setup lang="ts">
const exposed = { a: 1 }
defineExpose(exposed)
</script>
I've been using this workaround:
MyComponent.vue
<script setup lang="ts">
function printHello() {
console.log("Hello from MyComponent!");
}
defineExpose({
printHello,
});
</script>
<template>
<div />
</template>
App.vue
<script setup lang="ts">
import { ref, onMounted } from "vue";
import MyComponent from "./MyComponent.vue";
// This could be extracted into a type helper: type ComponentType<C> = typeof C extends new () => infer T ? T : never;
type ComponentType = typeof MyComponent extends new () => infer T ? T : never;
const myRef = ref<ComponentType>();
onMounted(() => {
console.log(myRef.value?.printHello)
});
</script>
<template>
<MyComponent ref="myRef" />
</template>
I have same problem, when run
vue-tsc
will show errorerror TS2339: Property 'getFormValue' does not exist on type '{ $: ComponentInternalInstance; $data: {}; $props: Partial<{}> & Omit<Readonly<{ page?: unknown; forms?: unknown; } & {} & { page?: PageService | undefined; forms?: TableFormConfig[] | undefined; }> & VNodeProps & AllowedComponentProps & ComponentCustomProps, never>; ... 10 more ...; $watch(source: string | Function...'.
but i don't konw how to fixed it
now update to the latest vue-tsc can fixed it
@xiaoxiangmoe Sorry but I don't think so (Volar v0.33.10). I've got still the same error when I use const myRef = ref<null | InstanceType<typeof MyComponent>>(null);
so I must still use the @eggplantiny workaround to make tsc happy. The same state in #5035, not fixed.
@fengjrrz This feature was added in volar v0.31.1
Same problem with IntelliJ IDEA 2021.3.3 and vue-tsc 0.33.9
I was getting:
error TS2339: Property 'openModal' does not exist on type
Until I updated my vue-tsc from 0.29.8. Here's the fix:
npm install "vue-tsc@>=0.34.10"
@fengjrrz This feature was added in volar v0.31.1
Same problem with IntelliJ IDEA 2021.3.3 and vue-tsc 0.33.9
Same problem with IDEA 2022.2.1 and vue-tsc 1.0.8
const galleryDetailRef = ref<InstanceType<typeof GalleryDetail>>();
用 InstanceType
可以获取提示,但是和 defineExpose 没有联系,他会把组件所有的属性都获取出来
I still got the same error with Vue Language Features (Volar) v1.0.9.
My component looks like this:
function myExposedMethod() {
console.log("test");
}
defineExpose({
myExposedMethod,
});
and I have a ref in the parent component like this:
const myCompRef = ref<InstanceType<typeof NameOfMyComponent> | null>(null);
but with the following line
myCompRef.value?.myExposedMethod();
I get the ts(2339)
error Property 'myExposedMethod' does not exist on type '{ $: ComponentInternalInstance; ...
EDIT: Please don't mind this comment. It's little embarrassing but I just had a spelling error... Seems to work perfectly fine!
I still got the same error with Vue Language Features (特征) (Volar) v1.0.9.
My component looks like this:
function myExposedMethod() { console.log("test"); } defineExpose({ myExposedMethod, });
and I have a ref in the parent component like this:
const myCompRef = ref<InstanceType<typeof NameOfMyComponent> | null>(null);
but with the following line
myCompRef.value?.myExposedMethod();
I get the
ts(2339)
errorProperty 'myExposedMethod' does not exist on type '{ $: ComponentInternalInstance; ...
EDIT: Please don't mind (介意) this comment. It's little embarrassing (难堪) but I just had a spelling (拼写) error... Seems to work perfectly (完全) fine!
You can try this way
import NameOfMyComponent from '...your component address'
interface INewNameOfMyComponentType
extends ref<InstanceType<typeof NameOfMyComponent>> {
myExposedMethod(): void
}
const myCompRef = ref<INewNameOfMyComponentType>()
then you can use the method
myCompRef.value?.myExposedMethod();
I used this way to solve my problem, you can try it too 😊
I am bumping into this issue as well, more or less, when I do this:
const DoneList = ref<InstanceType<typeof ArrangeableList | null>>(null);
I get the issue, (after turning Volar into takeover mode):
Type '<PayloadType extends object>(__VLS_props: Props & VNodeProps & AllowedComponentProps & ComponentCustomProps, __VLS_ctx?: Pick<{ props: Props; expose(exposed: { ...; }): void; attrs: any; slots: { ...; }; emit: (e: "dropItem", item: MovingItem<PayloadType>) => void; }, "attrs" | ... 1 more ... | "slots">, __VLS_setup...' does not satisfy the constraint 'abstract new (...args: any) => any'.
Type '<PayloadType extends object>(__VLS_props: Props & VNodeProps & AllowedComponentProps & ComponentCustomProps, __VLS_ctx?: Pick<{ props: Props; expose(exposed: { ...; }): void; attrs: any; slots: { ...; }; emit: (e: "dropItem", item: MovingItem<...>) => void; }, "attrs" | ... 1 more ... | "slots">, __VLS_setup?: { ......' provides no match for the signature 'new (...args: any): any'.ts(2344)
Maybe it has something to do with the component being generic?
I found this, may help you: @femans https://github.com/vuejs/language-tools/issues/3206#issuecomment-1624541884