Skip to content
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

Passing slots through in wrapper component #11478

Open
tminich opened this issue Jun 29, 2020 · 2 comments
Open

Passing slots through in wrapper component #11478

tminich opened this issue Jun 29, 2020 · 2 comments

Comments

@tminich
Copy link

tminich commented Jun 29, 2020

Version

2.6.11

Reproduction link

https://codesandbox.io/s/romantic-mahavira-1s4v6

Steps to reproduce

See the content below "Wrapper"

What is expected?

All three variants, deprecated and new syntax in either order, should produce the same result

What is actually happening?

All produce different results. In the example the final 'new syntax' variant seems fine, but it doesn't work with Vuetify (not sure if this should be considered their issue ore Vue's)


Passing slots through to child components is, according to what you usually find on the net, done with this syntax:

<slot v-for="(_, name) in $slots" :name="name" :slot="name" />
<template v-for="(_, name) in $scopedSlots" :slot="name" slot-scope="slotData"><slot :name="name" v-bind="slotData" /></template>

Unfortunately this uses the slot and slot-scope attributes which are marked as deprecated.
As a current replacement, the following syntax seems to be the correct one:

<template v-for="(_, name) in $scopedSlots" v-slot:[name]="slotData">
  <slot :name="name" v-bind="slotData"/>
</template>
<template v-for="(_, name) in $slots" v-slot:[name]>
  <slot :name="name"/>
</template>

Unfortunately this does not give the same result as the old syntax. Depending on the order in which scoped and non-scoped slots are passed, either scoped slots are not passed at all or non-scoped slots are only passed in $scopedSlots, which Vuetify (and maybe other libraries?) ignores for slots that are not supposed to be scoped.

@nandin-borjigin
Copy link

nandin-borjigin commented Aug 31, 2020

I've achieved behavioral consistency among three variants by renaming second iteration element to something other than name. What I have noticed from analyzing AST generated from TestWrapper is: for some reason, likely a bug, v-slot:[name] in second iteration (i.e. v-for) unexpectedly overrides the former one and results in this inconsistency.

You are likely to be able to "tweak" your problem at hand by renaming the iteration variable:

<template v-for="(_, name) in $scopedSlots" v-slot:[name]="slotData">
  <slot :name="name" v-bind="slotData"/>
</template>
<template v-for="(_, someOtherName) in $slots" v-slot:[someOtherName]>
  <slot :name="someOtherName"/>
</template>

I'd dig deeper to find the underlying problem, but hope this tweak could help you meanwhile.


Update:

Following facts together lead to the inconsistency OP described:

  1. Any slot content denoted by the new syntax v-slot is considered as scoped slot in AST (likely for performance optimization).
    el.slotScope = slotBinding.value || emptySlotScopeToken // force it into a scoped slot for perf
  2. Scoped slots are collected at their parent AST node (test-component node for this case), indexed by their name (i.e. the part after the colon in the v-slot syntax). And dynamic names (those surrounded by brackets) are not treated specially when being a map index.
    el.slotTarget = name

    if (element.slotScope) {
    // scoped slot
    // keep it in the children list so that v-else(-if) conditions can
    // find it as the prev node.
    const name = element.slotTarget || '"default"'
    ;(currentParent.scopedSlots || (currentParent.scopedSlots = {}))[name] = element
  3. As for deprecated old syntax <slot :slot="name">, this slot node is treated as a child node of test-component node on AST, thus separated from the scoped slots collected by test-component.

Fact 1 and 2 causes second iteration in one test-component to override the first iteration's contribution to scopedSlot property of test-component node, as long as the second iteration uses same identifier as the first one for the iteration element.
Fact 3 happened to prevent this collision by treating <slot :slot="name"> as a child node.

I want to ping @yyx990803 here to ask for a comment on this. Whether it should be considered "working as intended" or as a bug?

@erichorne
Copy link

I think I'm running into a similar issue here in 2.6.12. Using the v-slot:[scopedSlot]="slotData" syntax in the template, slots provided without any slot properties do not get passed on.
<template v-for="(_, scopedSlot) of $scopedSlots" v-slot:[scopedSlot]="slotProps"><slot :name="scopedSlot" v-bind="slotProps"></slot></template>

will not pass through when the parent stipulates:
<test-component><template #label>my label</template></test-component>
but it will pass through when the parent stipulates:
<test-component><template #item={item}>My item: {{item}}</template></test-component>

However, the first stipulation (#label) will pass through if the test-component does not assign slotProps, like so:
<template v-for="(_, scopedSlot) of $scopedSlots" v-slot:[scopedSlot]><slot :name="scopedSlot"></slot></template>

I've tried v-slot:[scopedSlot]="{p = {}}", v-slot:[scopedSlot]="p", v-slot:[scopedSlot]="p={}" and v-slot:[scopedSlot]="{...p}", but none of these combinations work (and the raw p={} fails to compile even though that is a value function parameter expression). I was surprised that {...p} didn't work.

For now, I use @Nandiin's workaround of different names with $scopedSlots and $slots even though we really aren't supposed to use $slots anymore, at least not in 2.6.x

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants