Vue: Allow a single slot to be rendered for multiple times

Created on 16 Jul 2018  ·  12Comments  ·  Source: vuejs/vue

What problem does this feature solve?

Currently if we render a slot for multiple times (eg. inside a v-for loop), we'll get

Duplicate presence of slot "${name}" found in the same render tree - this will likely cause render errors.

I know it's intentional to prevent render errors (like losing correct data binding) but consider the following use case: I have a v-breadcrumbs component which has a default item separator of a text node /, and I want to allows users to specify their own separators which can be anything, like an <v-icon> component.

Currently if I want to make it work, I have to define a scoped slot in the v-breadcrumbs component and bind nothing to it:

<slot name="sep" v-bind="{}">/</slot>

And component users must define slot-scope on it and not use anything from it:

<v-breadcrumbs>
  <v-icon slot="sep" slot-scope="_" name="angle-right"/>
</v-breadcrumbs>

And currently Vue is using slots as fallbacks for scoped slots with the same name. Whether the users can use a slot doesn't rely on if they want data from slot scope, but on whether the slot is gonna be rendered for multiple times inside the component self, this may raise more confusion for our users (like #8175).

So why don't we just get rid of such caveats and let a slot to be rendered more than once? We can clone them on duplication to prevent render errors as we already run the scoped slot function each time anyway. This makes the logic a lot simpler for users IMO: if we do not expect to use data from the slot scope, just use a slot, otherwise we'll always have to declare slot-scope to create the binding.

In addition, this would make documenting the (scoped) slots of a component more consistent, we just declare the data structure for a slot scope and users can decide if they want some data to bind to the slot-scope and don't need to care about how many times it will be rendered.

What does the proposed API look like?

No additional API needed, just clone the slot nodes upon render and remove the waring about duplicated slots.

It may be implemented in userland today in a quite hacky way, see: https://codesandbox.io/s/lp11y2wovz

I just tweaked the cloneVNode function from the Vue core a little bit and it seemed to be working as expected using render function (without losing reactivity and event binding). There maybe some other edge situations I missed so correct me if it's not such trivial.

feature request

Most helpful comment

Resolved by 530ca1b2db315fbd0e360807b2031d26665c5d3d and shipped with [email protected]. Cheers! 🎉

All 12 comments

Why duplicate presence of slot would cause errors? I think it's common to resue slot. Maybe we should remove this warning.

@liximomo See this link I posted earlier. It's not just as easy as “removing the warning”.

@Justineo I think this is because the vnode of the slot will always ref to the last rendered HtmlElement, so only the last get patched.

This is possible to do with render functions instead of templates. There may well be side effects to doing this, though it's worked for my use cases so far.

I do something similar in https://github.com/ky-is/vue-separator which coincidentally can be used for breadcrumb-style UI :)

@ky-is

This is why Vue currently doesn't allow slots to be rendered multiple times as I understand. You are losing reactivity here.

See https://codesandbox.io/s/3kr81nl92p

@Justineo Thanks for the link! Makes sense, I'll have to document that.

why not just convert "static" slots to be compiled to functions like like scoped slots (and unify them). the migration path isnt too hard to implement either, just replace each slot in vm.$slots with a property accessor in dev mode that triggers a deprecation warning and create a scoped slot function in vm.$scopedSlots:

for(const name in data.slots) {
  if(!data.scopedSlots[name]) data.scopedSlots[name] = () => cloneVNode(data.slots[name]);
}

@backbone87

It's actually very close to what I'm proposing (we may have to recursively clone all children as well).

As I understand this update isn't a breaking change. For library authors if they switch scoped slots to slots in some cases it also won't break those using templates. Library authors can also provide transparent updates for their users by manually fallback $scopedSlots to $slots if they are using render functions.

I mean why have "normal" $slots anyway? From a dev history perspective $slots was just there before $scopedSlots, but in retrospect the scoped slot approach is more flexible. But there is no need to have split approaches anymore. It is more work for the child component that has to check $scopedSlots and $slot. Just deprecate $slots altogether, mirror its properties into cloning functions of $scopedSlots and trigger deprecation when trying to use $slots. No library author needs to do anything in this case, except when making a release for the next major vue version, when the deprecated stuff gets kicked out.

Please, make this happen!

another benefit of always using scopedSlots insteadof slots is lazy evaluation of slot content:
Imagine a component with the following template loading(:while="!item") {{ item.name }}
The loading comp wouldnt display the slot content until item becomes truthy, but "normal" slots are rendered eagerly causing errors despite the slot content not being displayed.

Resolved by 530ca1b2db315fbd0e360807b2031d26665c5d3d and shipped with [email protected]. Cheers! 🎉

Was this page helpful?
0 / 5 - 0 ratings