Closed benruehl closed 2 years ago
This is expected behavior of TypeScript. You need to change your implementation if use TS. (Change to JS / GlobalComponent / merge to single component...)
Thank you for the quick response.
If it would be an issue with typescript why does the typescript compiler run successfully then? Just to remember: The app builds and runs perfectly fine. There are no runtime or compile time errors. In addition, we opened the project in Webstorm which does not have any issues with it either.
This is only a problem in .vue and .ts files where setup returns exports. If you write your vue code in tsx where setup returns a render function, mutually recursive components work, and type checking works too.
Foo.tsx
import { defineComponent, ref } from "vue";
import Bar from "./Bar";
interface FooProps {
a: number;
}
export default defineComponent<FooProps>({
setup(props) {
const x = ref(0);
return () => <Bar a={x.value} />;
},
});
Bar.tsx
import { defineComponent, ref } from "vue";
import Foo from "./Foo";
interface BarProps {
a: number;
}
export default defineComponent<BarProps>({
setup(props) {
const x = ref(0);
return () => <Foo a={x.value} />;
},
});
The above code works without any type checking errors. So if you really want proper type-checking, tsx is really great. But the downside of tsx is that I can't use vue directives like v-if
and v-for
and other syntactic sugars.
I found a workaround to beat the typechecker, but you cannot use <script setup lang="ts">
. You have to use <script lang="ts">
.
Foo.vue
(and similarly with Bar.vue
)
<template>
<Bar :a="x" />
</template>
<script lang="ts">
import { defineComponent, DefineComponent, ref } from "vue";
import Bar, { BarProps } from "./Bar";
export interface FooProps {
a: number;
}
export default defineComponent({
setup() {
const _Bar: any = Bar; // putting directly in return doesn't work
const x = ref(0);
return {
Bar: _Bar as DefineComponent<BarProps>,
x,
};
}
});
</script>
Typechecking of the Foo and Bar props work as expected.
I found a way to get it to work with script setup.
Foo.vue
<template>
<Bar :a="x" />
</template>
<script setup lang="ts">
import { defineAsyncComponent, DefineComponent, ref } from "vue";
import type { BarProps } from "./Bar.vue";
export interface FooProps {
a: number;
}
const Bar = defineAsyncComponent<DefineComponent<BarProps>>(() => import("./Bar.vue") as any);
const x = ref(0);
</script>
Simply defining an async component will cause recursive type inference between the two components, and the type checker will give up. You prevent recursion by typing the module as any
, and you enable type checking by letting the compiler know the type is DefineComponent<BarProps>
.
Hope this helps whoever is trying to get recursive components working.
@profound7 thanks, works perfectly :)
Since Volar as VSC extension doesn't really have docs I wonder where this could be documented 🤔
@zigomir nice to have a pr for add it to https://github.com/johnsoncodehk/volar/tree/master/extensions/vscode-vue-language-features#note for now. :)
We have two components that import each other. This works fine in Vue but leads to lots of errors inside the components' templates displayed in vscode.
We use Vue 3 single-file components with
setup
tag. Our components look roughly like this:MonitorChain.vue
MonitorChainDialog .vue
The displayed errors look like this:
Every property and imported component of both of the components is marked with "Cannot find name 'X'".