core icon indicating copy to clipboard operation
core copied to clipboard

expose and ts types

Open WalkAlone0325 opened this issue 2 years ago • 8 comments

Vue version

^3.2.39

Link to minimal reproduction

https://stackblitz.com/edit/vitejs-vite-kmnqvn?file=src%2FApp.vue,src%2Fcomponents%2FHelloWorld.vue&terminal=dev

Steps to reproduce

请使用 VSCode 打开

What is expected?

  1. 使用 setup 函数必须 return expose 的方法才能使 ts 的类型检测通过
  2. 如果使用 render 或者 tsx 的方式,无法显视的 return, ts 类型检查就找不到 expose 的属性

What is actually happening?

expose() 和 renturn {}

System Info

System:
    OS: macOS 12.5.1
    CPU: (8) arm64 Apple M1 Pro
    Memory: 5.17 GB / 32.00 GB
    Shell: 5.8.1 - /bin/zsh
  Binaries:
    Node: 16.16.0 - ~/.nvm/versions/node/v16.16.0/bin/node
    Yarn: 1.22.19 - /opt/homebrew/bin/yarn
    npm: 8.11.0 - ~/.nvm/versions/node/v16.16.0/bin/npm
  Browsers:
    Chrome: 105.0.5195.102
    Safari: 15.6.1
  npmPackages:
    vue: ^3.2.37 => 3.2.39

Any additional comments?

No response

WalkAlone0325 avatar Sep 11 '22 03:09 WalkAlone0325

use render property.

render by h fn

<script lang="ts">
import { defineComponent, h } from "vue";

export default defineComponent({
  name: "HelloWorld",
  setup() {
    const msg = () => {
      console.log("msg");
    };
    return { msg };
  },
  render() {
    return h("div", "hello");
  },
});
</script>

render by jsx . add @vitejs/plugin-vue-jsx and set lang is tsx

<script lang="tsx">
import { defineComponent } from "vue";

export default defineComponent({
  name: "HelloWorld",
  setup() {
    const msg = () => {
      console.log("msg");
    };
    return { msg };
  },
  render() {
    return <div>HelloWorld</div>;
  },
});
</script>

Dedicatus546 avatar Sep 11 '22 05:09 Dedicatus546

InstanceType tool would expose many properties that you don't need.

image

or you can create a type to describe Comp instance instead of depend on InstanceType tool.

type CompInst = {
  msg: () => void;
}

in component

// Comp.vue
<script lang="tsx">
import { defineComponent } from "vue";
import { CompInst } from "./type.ts";

export default defineComponent({
  name: "Comp",
  setup() {
    const exposeObj: CompInst = {
      // type hint !
      msg: () => console.log("msg");
    }
    return {
       ...exposeObj
    };
  },
  render() {
    return <div>HelloWorld</div>;
  },
});
</script>

use

<script setup lang="ts">
import { onMounted, ref } from 'vue';
import Comp from './Comp.vue';
import { CompInst } from "./type.ts";

const compRef = ref<CompInst | null>(null);

onMounted(() => {
  // type hint ! and doesn't have any properties that you don't want to expose. 
  compRef.value?.msg();
});
</script>

<template>
  <div>
    <Comp ref="compRef" />
  </div>
</template>

Dedicatus546 avatar Sep 11 '22 06:09 Dedicatus546

thanks.

WalkAlone0325 avatar Sep 11 '22 06:09 WalkAlone0325

why not this

<script lang="ts">
import { defineComponent, h } from "vue";

export default defineComponent({
  name: "HelloWorld",
  setup(_props, { expose }) {
    const msg = () => {
      console.log("msg");
    };

    expose({ msg })
   
    return () => (
        h("div", "hello");
    )
  });
</script>

don't need return { msg }, use expose can auto mount component Instance.

WalkAlone0325 avatar Sep 11 '22 07:09 WalkAlone0325

i have a try, and it can't get type hint from ts by InstanceType<typeof HelloWorld>, maybe it is a type bug for vue

image

Dedicatus546 avatar Sep 11 '22 11:09 Dedicatus546

Yes, I also think so, if vue want to mount to the component instance, it must first return { xx }.

WalkAlone0325 avatar Sep 11 '22 11:09 WalkAlone0325

I used tsx files got the same problem.

// Demo.tsx
import { defineComponent} from "vue";

export default defineComponent({
  name: "HelloWorld",
  setup(_props, { expose }) {
    const msg = () => {
      console.log("msg");
    };

    expose({ msg })
   
    return () => (<div>xxx</div>)
  });
// app.tsx
import { defineComponent, onMounted } from 'vue'
import Demo from './Demo'

export default defineComponent({
  setup (props) {
    const el = ref<InstanceType<typeof Demo>>()

    onMounted(() => {
      console.log(el.value)
      el.value?.msg // Uncaught TypeError: t.value.msg is undefined
    })

    return () => <Demo ref={el}></Demo>
  }
})

https://github.com/vuejs/composition-api/issues/966

Is this the wrong type of vue3?

mengdu avatar Sep 11 '22 13:09 mengdu

Check these

https://github.com/vuejs/core/issues/4397

https://github.com/logaretm/vee-validate/blob/main/packages/vee-validate/src/Form.ts#L229C1-L248C3

sadeghbarati avatar Oct 15 '22 06:10 sadeghbarati

same problem

Hccake avatar May 23 '23 02:05 Hccake

Try vue-macros' exportRender feature with SFC? In that case volar will handle them correctly.

so1ve avatar Sep 02 '23 14:09 so1ve

same problem + 1,and use render is work for me

trcat avatar Jan 04 '24 05:01 trcat

This is really problematic, just lost 2 hours trying to do something really easy, but using generic components with defineExpose is impossible right now. We need a fix.

#4397 was closed and had no solution.

NathanAP avatar Jan 08 '24 19:01 NathanAP

This is not considered a bug, because there is no way in TypeScript for the expose() call to affect the return type of the parent defineComponent() call. It's not just possible.

With the latest version of vue-tsc, this is working correctly when using defineExpose() inside <script setup>. If you for some reason has to continue using defineComponent(), then you will have to declare the export the exposed type as an interface:

// use this in other components
export interface Exposed {}

defineComponent({
  setup(_, { expose }) {
    expose({ /* ... */ } satisfies Exposed)
  }
})

yyx990803 avatar Jul 17 '24 06:07 yyx990803