Subscribe on changes!
avatar
Jul 9th 2021

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;
  }
}>();
avatar
Jul 9th 2021

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.

avatar
Jul 13th 2021

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

avatar
Jul 13th 2021

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?

avatar
Jul 14th 2021

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 }" />
avatar
Jul 14th 2021

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.

avatar
Apr 10th 2022

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>();
avatar
Apr 27th 2022

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.

avatar
Nov 29th 2022

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.

avatar
Apr 3rd 2023

Done in #7982