How to import interface for defineProps
update: please see: #7394
https://github.com/vuejs/core/issues/4294#issuecomment-1316097560
Version
3.2.1
Reproduction link
https://github.com/Otto-J/vue3-setup-interface-errors/blob/master/src/components/HelloWorld.vue
Steps to reproduce
clone: git clone start: yarn && yarn dev open: master/src/components/HelloWorld.vue modify: import interface from './types'
What is expected?
no error
What is actually happening?
[@vue/compiler-sfc] type argument passed to defineProps() must be a literal type, or a reference to an interface or literal type.
in chinese: 我想把 props的interface抽出去,但是会报错,如果interface组件里定义的就正常渲染 in english: I want to extract the interface of props, but an error will be reported. If the interface component is defined, it will render normally
Currently complex types and type imports from other files are not supported. It is theoretically possible to support type imports in the future.
We'll mark it as an enhancement for the future.
希望Vue团队能早日支持,现在使用vue3.2 defineProps()为Props定义单个单个类型还可以,若涉及到联合或交叉等复杂类型 defineProps就会编译错误, 这对于类型扩展是一件非常糟糕的事情
I have found a workaround in my case (#4758) that allows you to provide typing to your props (or its nested members) with an interface, even if the interface is imported from another file. The workaround is to rename your interface to something else.
import { Post as PostRenamed } from '@/models/forum/PostDef';
interface Props {
Args: {Post: PostRenamed };
}
const props = withDefaults(defineProps<Props>(), {});
I noticed that you have to do this renaming workaround if the interface name appears anywhere within your <template>
, even if the name doesn't necessarily correspond to your interface.
I also find a workaround, just use defineProps(ComplexType)
instead of defineProps<ComplexType>
. Even the ComplexType
is imported from other file, it works fine.
Despite a fairly similar setup to @phasetri's issue, I'm finding that the properties of the interface I'm trying to import resolve as attrs
instead of props
, which breaks what I see as expected and predictable functionality.
Control example:
<script setup lang="ts">
interface IThingie {
serial: string,
id: string,
startDate: Date | null,
endDate: Date | null
}
const props = withDefaults(defineProps<IThingie>(),{});
</script>
// In vue-devtools (values from route params)
props
{
"id": "123",
"serial": "asdfa",
"startDate": null,
"endDate" null
}
@phasetri's solution:
<script setup lang="ts">
import { IThingie as InterfaceRenamed } from './types';
interface Props {
Args: { IThingie: InterfaceRenamed }
}
const props = withDefaults(defineProps<Props>(),{});
</script>
// In vue-devtools (values from route params)
props
{
"Args": "undefined"
}
attrs
endDate: null
id: 123
serial: "asdfa"
startDate: null
In my team's particular case, we want to keep the interface(s) outside of the components and use them as generics. In that way, we'd be able to import them elsewhere such as services as needed. We can't seem to export them from a separate <script lang="ts">
section either when using <script setup lang="ts">
as we run into a compilation error (I'll submit an issue for that if there isn't one yet).
I guess all of this is to say a few things:
- that the renaming trick shouldn't be called a workaround specifically for importing interfaces
- to pose the question of whether or not exporting the interface from the Vue component itself would provide a similar effect to exporting from a
types.ts
file, if we could get around the compilation error caused by two<script lang="ts"> tags, one with
setup` - and to advocate that this change be treated with a bit more urgency than that of an enhancement for the next version.
That last one comes with some ignorance surrounding the difficulties of compiling Typescript from within Vue and the core team's release cadence, so I respect any disagreement with that request. It's just that, as a user, it does feel irksome that I can't import an interface from a file using a syntactical mode that's supposed to make Typescript adoption simpler.
To follow up on @cdvillard 's commentary -- In a world where there are many packages interacting with each other, such as Jest testing and Storybook's stories, being able to maintain a reusable interface that can be imported in our .vue components, and our other .ts files, makes a massive difference in our ability to adopt.
If it's possible to see this on a roadmap, that would greatly help with enterprise adoption. I love the simplicity and elegance of Vue, and in particular the new typescript features, but this is a major sticking point for us.
I appreciate all the hard work, I really do. But this is a bigger issue than it sounds.
If you are using Vite, you can use this plugin https://github.com/wheatjs/vite-plugin-vue-type-imports as a patch until this is officially supported.
@wheatjs , I gave the plugin a try before posting, but I couldn't get it working. Let me try again and see if I can at least post an issue with a basic recreation. I'm not sure if part of the issue had to do with running the compat build at the time, but that's no longer the case. I appreciate that you've made this though!
Created this issue on the plugin repo: https://github.com/wheatjs/vite-plugin-vue-type-imports/issues/4
I've gone all-in on Vue 3, TypeScript and the Composition API. However, the restriction of requiring object literals and interfaces being defined in the SFC source severely limits reuse.
I am attempting to define a common props interface in a composable module to replace the need for a mixin, extending Props interfaces as in #4989, and using mergeProps to merge an object with default values via withDefaults(). As I soon learned, that won't work. So the only solution is to either go crawling back to mixins and the Options API, or repeat literal code in dozens of files, inviting a maintenance nightmare.
Unless anyone has a 3rd option, for which I'm open to suggestions. The vite plugin mentioned above does not solve these issues for me.
This severely hinders wrapping 3rd party components via type script, as you have to copy and paste the interfaces into your SFC, greatly increasing maintenance burdens and destroying readability. Unless there is an easier way to do this?
<script setup lang="ts">
import { Datepicker, DatePickerProps } from 'vue3-date-time-picker'
defineProps<DatePickerProps>()
defineEmits(Datepicker.emit)
</script>
<template>
<div class="inputBox">
<datepicker
v-bind="$props"
class="..."
/>
</div>
</template>
e.g. the above is impossible currently.
Is there any way we can donate for specific issues? Or maybe open an issue hunt? I'd love to put in some extra donations specifically towards this ticket.
@nborko Would it be possible for you to work with actual props objects instead of TS interfaces? Or does that raise other issues in your workflow? i.e. this works fine:
// Props.ts
export const defaultProps = {
age: {
type: Number,
required: true,
},
job: {
type: String,
default: "Employee",
},
} as const;
<script setup lang="ts">
import { defaultProps } from './Props'
const props = defineProps({
...defaultProps,
name: String,
address: {
required: true,
}
})
</script>
If you need an actual type representation of the props as they are required from the parent, those can be extracted from these objects with a bit of type trickery.
If you are using Vite, you can use this plugin https://github.com/wheatjs/vite-plugin-vue-type-imports as a patch until this is officially supported.
Thank you so much for making this, @wheatjs ❤️ ! In the future, I'd also like to help fix this kind of issues. Do you have any pointers on what one should learn to be able to build or improve a plugin like yours ✨?
@LinusBorg That does work but negates the benefit of type checking the props using interfaces. Since the import in question is effectively a mixin, the props can be used in several places, and the interface may contain simple string literal types or something a lot more complex. It's helped me find a bunch of usage errors from the old 2.x code base that has worked in general, but typing everything has helped me see a lot of potential issues that I would have not otherwise found by inspection.
I've been playing around with casting individual properties as Prop<>, and that does help, as does const props: SomeInterface = defineProps()
. The type annotations on the interface had to change, however, since props with defaults can no longer be "undefined" in terms of typing.
Deep diving into the implementation of the compiler functions and macros has helped me resolve some of these issues, but my conclusion is that they promise much but deliver little in actual usage. I really like the idea of them and the syntactical sugar they provide, but at this time defineProps<T>
and withDefaults
are far too limited to be of any practical use for me.
Thanks for the response. I'm unsure about the verdict though.
Is using a props object with additional PropType<>
annotation able to do what you need or is something still missing? Is that a route that gives you typings for all props in the way you need?
Yes, I was probably too harsh, as the macros probably do work well for a good 95%+ of the people using the expected pattern. I was initially pretty frustrated when my meticulously developed type interfaces didn't work, and finally stumbled on this issue. I also didn't want to support two different coding paradigms in a single project, but in the end this may save me some work since I can copy/paste my old props definitions and annotate them instead of rewriting it from scratch.
PropType<>
isn't exported, but Prop<T, D>
is (which includes PropType
), which seems to be working so far. It doesn't provide a super fine-grained type checking on the prop definition itself (notably, when type is an array), but it does type checking everywhere it gets used, and that's a fair enough compromise for me.
PropType<>
isn't exported, butProp<T, D>
is (which includesPropType
),
PropType
certainly is exported by 'vue'
, I use it daily.
PropType<>
isn't exported, butProp<T, D>
is (which includesPropType
),
PropType
certainly is exported by'vue'
, I use it daily.
Huh, so it is. I stand corrected.
I'm now also affected by this issue and trying to workaround for 2 hours now without luck :slightly_frowning_face:
I want to extend a quasar QBtn like so
<script setup lang="ts">
import type { QBtnProps } from 'quasar';
defineProps<QBtnProps>();
</script>
<template lang="pug">
QBtn(
v-bind="$attrs",
color="primary",
flat,
icon-right="mdi-chevron-right"
)
</template>
Just want to forward all the attributes and have TypeScript completion with Volar, but I don't want to redefine every single attribute that QBtn already knows. Any hints how I can workaround that?
How about:
<script setup lang="ts">
import { QBtn } from 'quasar';
defineProps(QBtn.props);
</script>
How about:
<script setup lang="ts"> import { QBtn } from 'quasar'; defineProps(QBtn.props); </script>
Sadly that doesn't help at all, due to QBtn.props
is of type any
Not sure right now, but I may found the solution
<script lang="ts">
import type { QBtnProps } from 'quasar';
import { defineComponent } from 'vue';
export default defineComponent<QBtnProps>({});
</script>
<template lang="pug">
QBtn(color="primary", flat, icon-right="mdi-chevron-right")
</template>
This is not setup
, but at least it seems it will automatically just forward the props :eyes:
You can kind-of work around it by exporting only the parts of the interface you need. In this case I only really care about RouterLinkProps['to']
, I can add the other parts of RouterLink
as I need them.
<script setup lang="ts">
import { RouterLink } from 'vue-router'
import type { RouterLinkProps } from 'vue-router'
defineProps<{
to: RouterLinkProps['to']
}>()
</script>
<template>
<RouterLink :to="to" custom v-slot="{ href, navigate }">
<a
v-bind="$attrs"
:href="href"
@click="navigate"
class="inline-flex items-center justify-center p-0.5 overflow-hidden font-medium text-gray-900 rounded-lg bg-gradient-to-br from-green-400 to-blue-600 group group-hover:from-green-400 group-hover:to-blue-600 hover:text-white dark:text-white focus:ring-4 focus:ring-green-200 dark:focus:ring-green-800"
>
<span
class="px-5 py-2.5 transition-all ease-in duration-75 bg-white dark:bg-gray-900 rounded-md group-hover:bg-opacity-0"
>
<slot />
</span>
</a>
</RouterLink>
</template>
For me this problem should get all the importance for the Vue community, because it completelly defeats the purpose of using Vue at all (with Typescript, of course). The framework was rewritten with Typescript and a simple Prop cannot be typed with actual useful types (and interfaces) that everyone use!! It's insane!!!
Vue 3.0 should not be released without this feature !!!!!
We are already on Vue 3.2.29 :rofl: