- Start Date: 2019-01-14
- Target Major Version: 2.x & 3.x
- Reference Issues: vuejs/vue#7740, vuejs/vue#9180, vuejs/vue#9306
- Implementation PR: (leave this empty)
Introducing a new syntax for scoped slots usage:
-
New directive
v-slot
that unifiesslot
andslot-scope
in a single directive syntax. -
Shorthand for
v-slot
that can potentially unify the usage of both scoped and normal slots.
Using v-slot
to declare the props passed to the scoped slots of <foo>
:
<!-- default slot -->
<foo v-slot="{ msg }">
{{ msg }}
</foo>
<!-- named slot -->
<foo>
<template v-slot:one="{ msg }">
{{ msg }}
</template>
</foo>
When we first introduced scoped slots, it was verbose because it required always using <template slot-scope>
:
<foo>
<template slot-scope="{ msg }">
<div>{{ msg }}</div>
</template>
</foo>
To make it less verbose, in 2.5 we introduced the ability to use slot-scope
directly on the slot element:
<foo>
<div slot-scope="{ msg }">
{{ msg }}
</div>
</foo>
This means it works on component as slot as well:
<foo>
<bar slot-scope="{ msg }">
{{ msg }}
</bar>
</foo>
However, the above usage leads to a problem: the placement of slot-scope
doesn't always clearly reflect which component is actually providing the scope variable. Here slot-scope
is placed on the <bar>
component, but it's actually defining a scope variable provided by the default slot of <foo>
.
This gets worse as the nesting deepens:
<foo>
<bar slot-scope="foo">
<baz slot-scope="bar">
<div slot-scope="baz">
{{ foo }} {{ bar }} {{ baz }}
</div>
</baz>
</bar>
</foo>
It's not immediately clear which component is providing which variable in this template.
Someone suggested that we should allow using slot-scope
on a component itself to denote its default slot's scope:
<foo slot-scope="foo">
{{ foo }}
</foo>
Unfortunately, this cannot work as it would lead to ambiguity with component nesting:
<parent>
<foo slot-scope="foo"> <!-- provided by parent or by foo? -->
{{ foo }}
</foo>
</parent>
This is why I now believe allowing using slot-scope
without a template was a mistake.
If we can go back in time, I would probably change the semantics of slot-scope
- but:
-
That would be a breaking change now, and that means we will never be able to ship it in 2.x.
-
Even if we change it in 3.x, changing the semantics of existing syntax can cause a LOT of confusion for future learners that Google into outdated learning materials. We definitely want to avoid that. So, we have to introduce something new to differentiate from
slot-scope
. -
In 3.x, we plan to unify slot types so it's no longer necessary to differentiate between scoped vs. non-scoped slots (conceptually). A slot may or may not receive props, but they are all just slots. With this conceptual unification, having
slot
andslot-scope
being two special attributes seems unnecessary, and it would be nice to unify the syntax under a single construct as well.
-
It can be used on
<template>
slot containers to denote slots passed to a component, where the slot name is expressed via the directive argument:<foo> <template v-slot:header> <div class="header"></div> </template> <template v-slot:body> <div class="body"></div> </template> <template v-slot:footer> <div class="footer"></div> </template> </foo>
If any slot is a scoped slot which receives props, the received slot props can be declared using the directive's attribute value. The value of
v-slot
works the same way asslot-scope
, so JavaScript argument destructuring is supported:<foo> <template v-slot:header="{ msg }"> <div class="header"> Message from header slot: {{ msg }} </div> </template> </foo>
-
v-slot
can be used directly on a component, without an argument, to indicate that the component's default slot is a scoped slot, and that props passed to the default slot should be available as the variable declared in its attribute value:<foo v-slot="{ msg }"> {{ msg }} </foo>
Let's review whether this proposal achieves our goals outlined above:
-
Still provide succinct syntax for most common use cases of scoped slots (single default slot):
<foo v-slot="{ msg }">{{ msg }}</foo>
-
Clearer connection between scoped variable and the component that is providing it:
Let's take another look at the deep-nesting example using current syntax (
slot-scope
) - notice how slot scope variables provided by<foo>
is declared on<bar>
, and the variable provided by<bar>
is declared on<baz>
...<foo> <bar slot-scope="foo"> <baz slot-scope="bar"> <div slot-scope="baz"> {{ foo }} {{ bar }} {{ baz }} </div> </baz> </bar> </foo>
This is the equivalent using the new syntax:
<foo v-slot="foo"> <bar v-slot="bar"> <baz v-slot="baz"> {{ foo }} {{ bar }} {{ baz }} </baz> </bar> </foo>
Notice that the scope variable provided by a component is also declared on that component itself. The new syntax shows a clearer connection between slot variable declaration and the component providing the variable.
<!-- old -->
<foo>
<template slot-scope="{ msg }">
{{ msg }}
</template>
</foo>
<!-- new -->
<foo v-slot="{ msg }">
{{ msg }}
</foo>
<!-- old -->
<foo>
<div slot-scope="{ msg }">
{{ msg }}
</div>
</foo>
<!-- new -->
<foo v-slot="{ msg }">
<div>
{{ msg }}
</div>
</foo>
<!-- old -->
<foo>
<bar slot-scope="foo">
<baz slot-scope="bar">
<template slot-scope="baz">
{{ foo }} {{ bar }} {{ baz }}
</template>
</baz>
</bar>
</foo>
<!-- new -->
<foo v-slot="foo">
<bar v-slot="bar">
<baz v-slot="baz">
{{ foo }} {{ bar }} {{ baz }}
</baz>
</bar>
</foo>
<!-- old -->
<foo>
<template slot="one" slot-scope="{ msg }">
text slot: {{ msg }}
</template>
<div slot="two" slot-scope="{ msg }">
element slot: {{ msg }}
</div>
</foo>
<!-- new -->
<foo>
<template v-slot:one="{ msg }">
text slot: {{ msg }}
</template>
<template v-slot:two="{ msg }">
<div>
element slot: {{ msg }}
</div>
</template>
</foo>
<!-- old -->
<foo>
<bar slot="one" slot-scope="one">
<div slot-scope="bar">
{{ one }} {{ bar }}
</div>
</bar>
<bar slot="two" slot-scope="two">
<div slot-scope="bar">
{{ two }} {{ bar }}
</div>
</bar>
</foo>
<!-- new -->
<foo>
<template v-slot:one="one">
<bar v-slot="bar">
<div>{{ one }} {{ bar }}</div>
</bar>
</template>
<template v-slot:two="two">
<bar v-slot="bar">
<div>{{ two }} {{ bar }}</div>
</bar>
</template>
</foo>
-
Introducing a new syntax introduces churn and makes a lot of learning materials covering this topic in the ecosystem outdated. New users may get confused by discovering a new syntax after going through existing tutorials.
- We need good documentation updates on scoped slots to help with this.
-
The default slot usage
v-slot="{ msg }"
doesn't precisely convey the concept thatmsg
is being passed to the slot as a prop.
- New special attribute
slot-props
(as a previous version of this draft) - Directive (
v-scope
) instead of a special attribute as originally proposed in #9180; - Alternative shorthand symbol (
&
) as suggested by @rellect here
The change is fully backwards compatible, so we can roll it out in a minor release (planned for 2.6).
slot-scope
is going to be soft-deprecated: it will be marked deprecated in the docs, and we would encourage everyone to use / switch to the new syntax, but we won't bug you with deprecation messages just yet because we know it's not a top priority for everyone to always migrate to the newest stuff.
In 3.0 we do plan to eventually remove slot-scope
, and only support the new syntax. We will start emitting deprecation messages for slot-scope
usage in the next 2.x minor release to ease the migration to 3.0.
Since this is a pretty well defined syntax change, we can potentially provide a migration tool that can automatically convert your templates to the new syntax.