Subscribe on changes!

script-setup use `defineExpose`, component should have typescript declaration

avatar
Aug 20th 2021

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

avatar
Aug 26th 2021

Calling ctx.expose while using setup function in defineComponent seems to have the same problem

avatar
Sep 26th 2021

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

avatar
Oct 26th 2021

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...

avatar
Oct 30th 2021

Having the same issue. Any Workaround available?

avatar
Oct 31st 2021

Having the same issue here.

avatar
Nov 2nd 2021

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

avatar
Nov 2nd 2021

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.

avatar
Dec 2nd 2021

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.

avatar
Dec 6th 2021

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.

avatar
Dec 6th 2021

This should be now fixed by @NoelDeMartin here #5035

avatar
Dec 6th 2021

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

avatar
Dec 6th 2021

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.

avatar
Dec 10th 2021
// 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 🙂

avatar
Dec 11th 2021

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>
avatar
Feb 11th 2022

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>
avatar
Feb 11th 2022

@leonzalion Maybe you should use const myRef = ref<null | InstanceType<typeof MyComponent>>(null);

avatar
Feb 11th 2022

This feature was added in volar v0.31.1

avatar
Mar 22nd 2022

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

now update to the latest vue-tsc can fixed it

avatar
Mar 27th 2022

is there any progress about the issue?

avatar
Mar 28th 2022

@fengjrrz This feature was added in volar v0.31.1

avatar
Mar 28th 2022

@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.

avatar
Mar 28th 2022

@StazriN Can you provide a minimal reproduction demo?

avatar
Mar 28th 2022

@fengjrrz This feature was added in volar v0.31.1

Same problem with IntelliJ IDEA 2021.3.3 and vue-tsc 0.33.9

avatar
Apr 24th 2022

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"
avatar
Oct 30th 2022

@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

avatar
Nov 8th 2022
const galleryDetailRef = ref<InstanceType<typeof GalleryDetail>>();

InstanceType 可以获取提示,但是和 defineExpose 没有联系,他会把组件所有的属性都获取出来

avatar
Nov 12th 2022

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!

avatar
Dec 29th 2022

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!

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 😊

avatar
May 1st 2023

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?