Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

script-setup use defineExpose, component should have typescript declaration #4397

Closed
liuzw2579 opened this issue Aug 20, 2021 · 30 comments
Closed

Comments

@liuzw2579
Copy link

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

@klwfwdk
Copy link
Contributor

klwfwdk commented Aug 26, 2021

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

@zhuchentong
Copy link

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

@MichaelGitArt
Copy link

MichaelGitArt commented Oct 26, 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...

@Blackfaded
Copy link

Having the same issue. Any Workaround available?

@nyancodeid

This comment has been minimized.

@luyuhong0329
Copy link

luyuhong0329 commented Nov 2, 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,
})

@johnsoncodehk
Copy link
Member

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.

@aradalvand
Copy link

aradalvand commented Dec 2, 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.

@NoelDeMartin
Copy link
Contributor

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.

@DannyFeliz
Copy link

This should be now fixed by @NoelDeMartin here #5035

@aradalvand
Copy link

aradalvand commented Dec 6, 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

@NoelDeMartin
Copy link
Contributor

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.

@eggplantiny
Copy link

eggplantiny commented Dec 10, 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 🙂

@xiaoxiangmoe
Copy link
Contributor

xiaoxiangmoe commented Dec 11, 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>

@leonsilicon
Copy link
Contributor

leonsilicon commented Feb 11, 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>

@xiaoxiangmoe
Copy link
Contributor

xiaoxiangmoe commented Feb 11, 2022

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

@xiaoxiangmoe
Copy link
Contributor

This feature was added in volar v0.31.1

@JianJroh
Copy link

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

@fengjrrz
Copy link

is there any progress about the issue?

@xiaoxiangmoe
Copy link
Contributor

@fengjrrz
This feature was added in volar v0.31.1

@StazriN
Copy link

StazriN commented Mar 28, 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.

@xiaoxiangmoe
Copy link
Contributor

@StazriN Can you provide a minimal reproduction demo?

@JackieCheung
Copy link

@fengjrrz This feature was added in volar v0.31.1

Same problem with IntelliJ IDEA 2021.3.3 and vue-tsc 0.33.9

@ubershmekel
Copy link

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"

@DoubleLazyZ
Copy link

DoubleLazyZ commented Oct 30, 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

@lesonky
Copy link

lesonky commented Nov 8, 2022

const galleryDetailRef = ref<InstanceType<typeof GalleryDetail>>();

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

@54mu3l
Copy link

54mu3l commented Nov 12, 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!

@Plumliil
Copy link

Plumliil commented Dec 29, 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 😊

@femans
Copy link

femans commented May 1, 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?

@youthlin
Copy link

I found this, may help you: @femans
vuejs/language-tools#3206 (comment)

@github-actions github-actions bot locked and limited conversation to collaborators Sep 7, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests