-
-
Notifications
You must be signed in to change notification settings - Fork 8.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: add defineSlots
macro and slots
option
#7982
Conversation
e7c7901
to
652ac28
Compare
defineSlots
macro and slots
option
Not a fan of the tuple syntax, because it does not accurate describes the javascript behaviour: <template>
<a>
<slot title="hello" something="sss"/>
</a>
</template> The slot will be called with an const __sfc__ = {}
import { renderSlot as _renderSlot, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
function render(_ctx, _cache) {
return (_openBlock(), _createElementBlock("a", null, [
_renderSlot(_ctx.$slots, "default", {
title: "hello",
something: "sss"
})
]))
}
__sfc__.render = render
__sfc__.__file = "Comp.vue"
export default __sfc__ You can see
That's not the correct type, the correct type is: expectType<((param: { foo: string, bar: number }) => VNode | VNode[])>(slots.default) I would also argue that the See playground |
@pikax Thanks for your review. I've changed the style of the definition. But I think it should be expectType<undefined | ((param: { foo: string; bar: number }) => VNode[])>(
slots.default
) because if the parent component didn't pass the slot, the slot is undefined. And if the parent component did, it's |
I don't fully agree just because it would allow people to force users to declare a slot. using the same example <template>
<comp v-slot="a">
</comp>
</template> The typechecking should warn that the slot default is required as the user should be passing it. Like how required Also this get a bit more complicated as you can see here |
@pikax I agree with you. We should have a better DX about slots, but I think we need more discussions about it and we likely need to add runtime code. So maybe in another PR? |
I don't have a strong opinion on either be done on this or another PR, what I think is a bit odd the syntax be very similar to const props = defineProps<{
a?: string; // explicit optional
b: string;
}>();
expectType<number | undefined>(prop.a)
expectType<number>(prop.b)
const slots = defineSlots<{
a?: { title: string };
b: { title: string }; // must pass slot
}>()
expectType<undefined | (title: string)=> VNode[]>(slots.a)
expectType<(title: string)=> VNode[]>(slots.b) Otherwise they just feel different |
Would it be possible to type-check missing slots or would it rather be a task for eslint or other tools? |
It would be possible to do typechecking, altho not sure how Volar is currently doing (with JSX) but if used /cc @johnsoncodehk |
One more question - what about dynamic slot names? For example in many table components you can have slots related to table columns, const slots = defineSlots<{
[name in 'item.${string}']?: { title: string }; // not even sure if the syntax is valid
}>() |
Yes This would be possible: interface MyAwesomeType {
title: string;
age: number;
}
const slots = defineSlots<{
[name in `col.${keyof MyAwesomeType}`]?: { item: MyAwesomeType, index: number };
}>()
slots['col.age']
slots['col.title']
// @ts-expect-error not valid
slots['col.no'] |
It's supported in volar / vue-tsc v1.3.8 when enabled I'm not sure if it's appropriate to simplify the slots type input, it removes the possibility to check the children type for TSX. https://www.typescriptlang.org/docs/handbook/jsx.html#children-type-checking Even typed $slots as is doesn't add much boilerplate. const slots = defineSlots<{
- default?: { foo: string; bar: number }
+ default?(props: { foo: string; bar: number }): any /* or VNode[] */
}>() |
const slots = defineSlots<{
a?: { title: string };
}>()
--- expectType<undefined | (title: string)=> VNode[]>(slots.a)
+++ expectType<undefined | (scope: { title: string }) => VNode[]>(slots.a) There will be only one param for slot, so the first param should an object. Also there are actually two cases if we added optional property. - Whether the slot is optional or the scope of the slot is optional. const slots = defineSlots<{
a?: { title: string };
}>()
expectType<undefined | (scope: { title: string }) => VNode[]>(slots.a) // case 1
expectType<(scope?: { title: string }) => VNode[]>(slots.a) // case 2 |
@sxzz the second case is not possible, the object keys can be optional, but the first argument will always be present(as soon as you don't call it manually, which is not advised if you don't know what you're doing) |
@pikax I see and I updated the code. But there are lots of uses of call the slot directly without a scope param in many 3rd party libraries, even in unit tests of vue core.
I think there's a safer way to mark scope as optional const slots = defineSlots<{
default: { foo: string; bar: number }
optionalSlot?: string // it's an optional slot, but the scope is always present
optionalScope: undefined | string // it's a required slot, but the scope is optional (could be undefined)
}>() |
I agree with that/ Seems when you use SFC it will always pass an object playground |
/ecosystem-ci run |
📝 Ran ecosystem CI: Open
|
Update: added support so that <script setup lang="ts">
const slots = defineSlots<{
default(props: { foo: string; bar: number }): any // or VNode[]
}>()
</script> |
@@ -1428,6 +1494,7 @@ declare const MyButton: DefineComponent< | |||
ComponentOptionsMixin, | |||
EmitsOptions, | |||
string, | |||
{}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missed this part when I was reviewing - we cannot actually change the type arguments order of DefineComponent
because it's a publicly-exported type and used in library type definitions.
Just a note for the future: downstream libs like |
…ineSlots` macro and `slots` option (vuejs#7982)
Is it possible to use generic types in SlotsType? Function Signature only gives an example for props. |
Summary
This PR adds slots type checking for:
slots
option andSlotsType
utility type.<script setup lang="ts">
via the newdefineSlots
macro.defineSlots
in SFCsvueCompilerOptions.strictTemplates
is enabled intsconfig.json
.Basic Usage
<script setup lang="ts">
defineSlots()
only accepts a type parameter and no runtime arguments. The type parameter should be a type literal where the property key is the slot name, and the value is a slot function. The first argument of the function is the props the slot expects to receive, and its type will be used for slot props in the template. The returning value ofdefineSlots
is the same slots object returned fromuseSlots
.Some current limitations:
any
, but we may leverage it for slot content checking in the future.Note: a previous version of this feature (as of this PR) supported a shorter syntax
defineSlots<{ foo: { id: string }}>()
- but the type conversion required in this signature breaks when used along with generic types (ref: vuejs/language-tools#2758), so it was removed in 1279b17. Now the function syntax is required and the only supported syntax.Options API
This PR did not add any runtime code, but only included TypeScript types and SFC compiler features. Both
defineSlots
and theslots
option have no runtime implications and serve purely as type hints for IDEs andvue-tsc
.Related
RFC: vuejs/rfcs#192 (not quite according to RFC)