defineSlots for script setup
What problem does this feature solve?
(I'm sure I saw this mentioned somewhere before, but I can't find any issue nor discussion about it. Apologies if it's a dupe.)
For Typescript users, script setup
needs a defineSlots
macro to create strongly typed slots.
The problem is that today the code generated by script setup creates TS compilation errors.
The following template creates an error Binding element 'ok' implicitly has an 'any' type
.
(You have the same error even if you don't destructure the slot value.)
<my-dialog v-slot="{ ok, cancel }">
<button @click="ok()">Close</button>
</my-dialog>
This is quite a blocker because there's no good workaround:
- You can't add type annotations inside templates (and in this instance it would be very ugly very quickly);
- You can't add
ts-ignore
comments in the template. - This errors is only reported with
--noImplicitAny
but that's a strict flag that we really don't want to globally disable in our codebase.
Of course, on top of the TS error, having typed slots would be a nice improvement for Volar.
What does the proposed API look like?
As a quick-fix / workaround, I suggest that the compiler adds // @ts-ignore: implicit any
before the generated slot code.
A better, long term fix would be to allow TS users to type their slots, I suggest:
defineSlots<{
default: {
quantity: number;
order(): void;
}
}>();
A small post-scriptum: I have build errors when the consuming template is a script setup component.
How the component itself is declared seems irrelevant, so that probably means a slots: { ... }
addition to classical <script>
would keep everything consistent.
I don't know since when, but today I noticed that Volar is able to type slots that are inside components defined by script setup
, when they're used in the same project.
That's super cool and a great QoL improvement ✨
It would be super-duper awesome if there was a way to couple that Volar inferrence with a way to make props/slots generic. I put one real-life example of what it could achieve in this discussion: https://github.com/vuejs/rfcs/discussions/334#discussioncomment-997375
I'm curious what is the real benefit of type checking generated code here - vue-tsc
can type check *.vue
files directly so you'd preferably use that - which works on your source code providing more accurate error locations. Then you'd use esbuild
or transpile-only mode of TS for faster builds. Am I missing something?
Yes, I agree.
I opened this when my slot was implicitly typed as any
in IDE.
Now I think this was because the component providing the slot was a script-classic, as it seems that script-setup is inferred by Volar / vue-tsc.
There's no need for manually declaring defineSlots
if they can be automatically inferred.
Maybe this issue should have been that a slot: { default: ... }
would be welcome in script-classic? Or was my slot implicitly any
for a different reason at all? I could have been confused, this is all still experimental to me.
A few extra thoughts / considerations
(1) I'm building with TS not Esbuild, mainly because of const enums. Speed is always good but for production build is not the top priority. Doing 2 TS passes (one with vue-tsc
then regular build with TS) is a bit overkill, I opened this discussion about some possible solutions: https://github.com/johnsoncodehk/vue-tsc/discussions/48
(2) I am curious how this will all turn out with generic components, which would be a complex, albeit much needed, addition. For example, if you have a component that provides a slot value only one of its props is filled or has a specific type.
Consider a kind of Form component, that injects a validation
object iff its rules
property is set:
<template>
<form>
<slot :val="val" />
</form>
</template>
<script setup>
// Not sure what the syntax in script setup would be...
defineGeneric<M extends object, R extends Rules<M> | undefined>();
defineProps<{ model: M, rules?: R }>();
// This is injected in slot, its type should be Validation if rules props is not undefined, otherwise it's undefined.
const val: R extends Rules<M> ? Validation<M, R> : undefined;
</script>
It can all be modelled by TS type system, but when you use the component in a template, I have no idea if it would even be possible to infer the right types!
<!--
Here we need to infer:
M: { name: string }
R: { name: required }
So TS can then:
- Validate R satisfies Rules<M>
- Type val as `Validation<M, R>` (not undefined)
-->
<my-form :model="{ name: 'first' }" :rules="{ name: required }" v-slot="{ val }" />
<!--
This time we should infer:
R: undefined
Then val is `undefined` and trying to use it will result in a compilation error.
-->
<my-form :model="{ name: 'first' }" v-slot="{ val }" />
If the remaining utility is only related to generic components, I think it would be better discussed as part of the generic components proposal. So closing this one here.
If the remaining utility is only related to generic components, I think it would be better discussed as part of the generic components proposal. So closing this one here.
@yyx990803 如果使用volar开发,现在项目中能有scopedSlots props的类型提示,但是貌似是结合模版解析提示的。
但是如果构建一个第三方组件库,.vue
文件会编译成 js 文件,这时候会丢失slots的类型信息(因为现在的defineComponent里面没有slots的类型,所以生成的.d.ts文件貌似不能保留slots 的类型?),引用这类组件库就得不到scopedSlots props的类型提示。
所以在现有的defineComponents里加入slots类型变量,以及script setup
里加入defineSlots
好像有必要?
或者可以在现有的useSlots
上加上泛型?
const slots = useSlots<Slots>();
This should be reopened. I use the latest Volar yet I do not have any type interference for slot props. I get type any
just like the OP did.
I also find this feature useful when I have slots in child components and would like to define them in the Enclosing Component. I don't know any solution for this.