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

Proposal for new slots syntax #2

Merged
merged 8 commits into from Jan 16, 2019

Conversation

Projects
None yet
@yyx990803
Copy link
Member

yyx990803 commented Jan 14, 2019

Rendered

@znck

This comment has been minimized.

Copy link
Member

znck commented Jan 14, 2019

I had doubt about the shorthand syntax being invalid attribute name but HTML spec is very flexible. Any printable charcter except (=, /, ', ", <, >) is valid attribut name (https://html.spec.whatwg.org/multipage/parsing.html#attribute-name-state).

@znck

This comment has been minimized.

Copy link
Member

znck commented Jan 14, 2019

Is this valid syntax?

<foo>
  <template :(dynamicSlotName)="{ name }">
    <div>Hello {{ name }}</div>
  </template>
</foo>
@yyx990803

This comment has been minimized.

Copy link
Member Author

yyx990803 commented Jan 14, 2019

@znck I'd avoid that if I can. If you need that level of dynamism you probably should go render functions directly.

Edit: supporting it is not that difficult. The downside is dynamic slot names means the slots generated will not be "stable" and can require some optimizations to bail out.

@darrenjennings

This comment has been minimized.

Copy link

darrenjennings commented Jan 14, 2019

The ()="foo" reminds me of a closure or anonymous function and could be confusing to scan in the case where you have code something like:

<foo ()="foo">
  <bar ()="bar">
    <baz 
      ()="baz" 
      @close="() => isVisible = false">
      {{ foo }} {{ bar }} {{ baz }}
    </baz>
  </bar>
</foo>

I like the & shorthand per @rellect's suggestion. Would it be possible to do something like <foo &foo> as another shorthand when you don't need to destructure? Then you could do <foo &foo>, <foo &="foo">, or <foo &="{foo}">. Pointers are a confusing concepts in languages like C, so I could see this turning people off from the syntax if it's "just like pointers".

However, v-scope in conjunction with & provides nice symmetry with existing directives:

  • v-bind => :
  • v-on => @
  • v-scope => &
@znck

This comment has been minimized.

Copy link
Member

znck commented Jan 15, 2019

The ()="foo" reminds me of a closure or anonymous function and could be confusing to scan in the > > case where you have code something like:

The reasoning behind using () shorthand is that a slot compiles to a function (or equivalent to render prop in JSX) so it's meant to appear like function.

Also, it's better to use an inline expression instead of arrow function for events.
e.g. @close="isVisible = false"

@darrenjennings

This comment has been minimized.

Copy link

darrenjennings commented Jan 15, 2019

@znck you are right for events. Although it is not uncommon to see function properties that are not events which evaluate immediately, but still need to pass in properties from the template through an anon function. So a more common usage case would be something like:

<foo ()="foo">
  <bar ()="bar">
    <baz 
      ()="baz"
      :onBaz="() => someMethod(baz)">
      {{ foo }} {{ bar }} {{ baz }}
    </baz>
  </bar>
</foo>

However, it is not lost on me that the component author could implement onBaz as an event instead. I did this for vue-autosuggest (related issue) as I had come from a React background and was used to function props, which can be helpful when you need to return a value and not just emit. You could also just make someMethod return a function to avoid putting this in the template, but it's something I've run across so thought I'd share.

so it's meant to appear like function.

Ah I see, I wonder if the fact that you cannot pass in any arguments is also what is trippy. It's a function underneath but () is just shorthand, not a function itself and not useful in the template for passing in arguments or a dynamic slot name as you questioned earlier.

@dinhquochan

This comment has been minimized.

Copy link

dinhquochan commented Jan 15, 2019

I love this idea:

However, v-scope in conjunction with & provides nice symmetry with existing directives:

v-bind => :
v-on => @
v-scope => &

Sample:

<foo &="{ foo }">
  <bar &="{ bar }">
    <baz 
      &="{ baz }"
      :onBaz="() => someMethod(baz)"
      @input="() => otherMethod(bar)"
      >
      {{ foo }} {{ bar }} {{ baz }}
    </baz>
  </bar>
</foo>

Or:

<foo v-scope="{ foo }">
  <bar v-scope="{ bar }">
    <baz 
      v-scope="{ baz }"
      v-bind:onBaz="() => someMethod(baz)"
      v-on:input="() => otherMethod(bar)"
      >
      {{ foo }} {{ bar }} {{ baz }}
    </baz>
  </bar>
</foo>
@yyx990803

This comment has been minimized.

Copy link
Member Author

yyx990803 commented Jan 15, 2019

I think having a scoped slot and an expression that contains an arrow function is quite rare (it's definitely possible, but also definitely not common). BTW - in React callback props and render props all use arrow functions and it doesn't seem to be an issue. On the other hand, this can actually be solved at the syntax highlighting level (the parens will be colored differently).

My personal reservation against & is that it lacks a conceptual association to how scoped slots work internally (maybe it's because I have too much context on what they are compiled into), and that it doesn't "jump out" enough like parens do.

@CyberAP

This comment has been minimized.

Copy link

CyberAP commented Jan 15, 2019

Square brackets could be used to avoid confusion with Angular props and arrow functions.

<foo []="foo">
  <bar []="bar">
    <template [header]>
      123
    <template>
    <template [footer]="{ baz }">
      {{ foo }} {{ bar }} {{ baz }}
    </template>
  </bar>
</foo>
@CyberAP

This comment has been minimized.

Copy link

CyberAP commented Jan 15, 2019

This is not HTML compliant at all but I find this way of defining slot and scope props interesting:

<foo="foo">
  <bar="bar">
    <template:header>
      123
    </template>
    <template:footer="{ baz }">
      {{ foo }} {{ bar }} {{ baz }}
    </template>
  </bar>
</foo>
@dimensi

This comment has been minimized.

Copy link

dimensi commented Jan 15, 2019

v-scope says nothing about what is. how it's look with named slots?

<bar>
  <template slot="header" v-scope="{ foo }">{{ foo }}</template>
</bar>

in the form of slot="name" slot-scope="{foo}" , I constantly spend time to understand what is which. I believe that the v-scope situation will also put mental pressure on the developer.
This is why slot-props seems clearer to me than v-scope.
Why I do not like brackets () because they do not work as they should work.
In the react, the arguments are bracketed, and the arrow gives the result.

(in) => <Out>{in}</Out>

But in our case, the brackets point to the correct slot, and then pass it inside.

const { out } = (slot)
<tempalte>{{ out }}</template>

This is similar to react render props and not at the same time. In my opinion
In case with &
I see it like

const & = Component.$slots
const { result } = &.default()
render() { return <Out>{{ result }}</Out> }
@chrisvfritz

This comment has been minimized.

Copy link
Member

chrisvfritz commented Jan 15, 2019

Why a shorthand might not be appropriate for scoped slots

@yyx990803 I generally like the direction this proposal is going, except for the shorthand. I agree with @c01nd01r that at first glance, ()="foo" can look like an arrow function returning "foo" (e.g. ()=>"foo". But more broadly, I think we should reserve shorthands for features that are:

  1. extremely common (any generic Vue demo is likely to include both v-bind and v-on) and B), and
  2. generally very intuitive and well-understood by the community, including recent adopters.

Both v-bind and v-on meet these criteria. Since scoped slots don't meet either of them, I worry that:

  • it will take longer for people to become used to any shorthand, due to less frequent exposure to scoped slots, and
  • since scoped slots are already more difficult for many people to wrap their heads around, having to translate the shorthand while learning will only make it harder.

Other limitations of this proposal

  • This proposal still allows and encourages patterns like <slot v-bind="user" /> to save a few characters with slot-props="user" instead of slot-props="{ user }". As I mentioned in the other issue, I see this as an anti-pattern. The problem arises when the component wants to expose data other than the user. Then they have two choices: make a breaking change to their API or force this new property onto the user object, even though it may have very little to do with the user. Neither is a great option.

  • The problem remains that slot-scope/slot-props behaves like a v- prefixed directive rather than a special attribute. All other special attributes accept any string and can be bound to expressions that evaluate to any string. With the special destructuring syntax that also adds new properties to the scope, slot-scope/slot-props are really more like a directive such as v-for. I think this is part of what creates confusion around scoped slots.

  • The more I think about it, the more I realize it feels strange having a separate attribute for defining the slot vs the props provided by that slot. To get the full story of where some property is coming from, you have to look for slot-props, then a slot attribute, then the component name, which is one more step than it has to be.

An alternative idea: v-slot

Just as v-for isn't split into multiple attributes like v-iterate="items" and iteration-item="item", I wonder if we should combine the slot API into a single, v- prefixed directive, e.g. v-slot:

<!-- Default slot, without props -->
<foo>
  hello
</foo>

<!-- Named slot, without props -->
<foo>
  <template v-slot="one">
    hello
  </template>
</foo>

<!-- Default slot, with prop -->
<foo v-slot="{ foo }">
  {{ foo }}
</foo>

<!-- Default slot, with multiple props -->
<foo v-slot="{ foo, bar }">
  {{ foo }} {{ bar }}
</foo>

<!-- Default slot, with multiple, aliased props -->
<foo v-slot="{ foo as one, bar as two }">
  {{ one }} {{ two }}
</foo>

<!-- Named slot, with prop -->
<foo>
  <template v-slot="one with { foo }">
    hello
  </template>
</foo>

<!-- Dynamically named slot, without props -->
<foo>
  <template v-slot="[slotName]">
    hello
  </template>
</foo>

<!-- Dynamically named slot, with prop -->
<foo>
  <template v-slot="[slotName] with { foo }">
    hello
  </template>
</foo>

<!-- Nested default slots, with props -->
<foo v-slot="{ foo }">
  <bar v-slot="{ bar }">
    <baz v-slot="{ baz }">
      {{ foo }} {{ bar }} {{ baz }}
    </baz>
  </bar>
</foo>

<!-- Nested named/default slots, with props -->
<foo>
  <template v-slot="one with { baz }">
    <bar v-slot="{ bar }">
      {{ bar }} {{ baz }}
    </bar>
  </template>
  <template v-slot="two with { baz }">
    <bar v-slot="{ bar }">
      {{ bar }} {{ baz }}
    </bar>
  </template>
</foo>

A few notes:

  • Just as Evan proposed, v-slot would only be usable on root elements for default slots and <template> for named slots. As a sidenote, I think it would be great if we could show a warning when someone tries to access a prop shared by the default slot inside a named slot.

  • As for the exact syntax to join the slot name and props, I'm not sure whether with is the right choice. Maybe a colon would be better, e.g. v-slot="one: { foo }"/v-slot="[slotName]: { foo }", or something else I'm not thinking of? I chose with because its use here semantically matches its role in JS: to add new properties to the scope.

  • I'm not sure whether v-slot is exactly the right name or if it should be v-scope or something else. I'm personally leaning toward v-slot, since it would also replace the slot attribute. Seems like an easier transition.

Some advantages:

  • With this proposal, it's not possible to pull out all slot props as a single object, eliminating that less future-proof pattern in scoped slots.

  • All slot information will now be visible on a single attribute, so there will never be any need to search for it, even with multiple properties.

  • Adding new properties to the scope in a directive is more consistent with the rest of our API. I know this comes at the cost of diverging from the slot API for web components, but as long as we still use the word "slot", I think we get the best of both worlds, since the v- also makes it clear that this is a special implementation of the slot pattern that Vue provides.

  • A legitimate argument for spreading an object onto a slot with <slot v-bind="item" /> is that it allows users to call item whatever they want. This use case is addressed without the limitations of that pattern with the new as aliasing, e.g. v-slot="{ item as product }". As a bonus, the use of as here should be familiar to many since as serves the same purpose in JS.

Some disadvantages/risks:

  • If we introduce this in 2.6 with a soft deprecation of the old API, we essentially double the size of the slots API in the meantime, so that's more for users to learn.

  • This proposal would require new limitations on slot names: unless used dynamically, they cannot contain whitespace nor include the characters { and }. That means there may be some existing libraries that would require an update to be used with the new syntax. I don't think this is a huge deal, because with the soft deprecation, users would still be able to use the old API in the meantime.

  • Since this proposal does not allow pulling out all slot props into a single object, some libraries would require an update for this reason as well. Again, I don't think it's a huge deal and is a great way of gradually moving the community towards better practices.

@yyx990803

This comment has been minimized.

Copy link
Member Author

yyx990803 commented Jan 15, 2019

@chrisvfritz

I appreciate the input but I find the v-slot proposal quite flawed. The examples require big mental overhead to parse:

  • Multiple different micro syntax can appear in the same position
  • It's no longer straight up JavaScript destructuring (current slot-scope syntax has one simple rule: anything you can put in a JavaScript function's argument position)
  • Semantics change completely from not-so-obvious syntax change (e.g. static named slot vs. default slot with destructuring)

Sidenote: I think we should refrain from coming up with completely different proposals under an RFC to avoid sidetracking. Separate proposals aimed at solving the same problem can be submitted as a separate RFC so it gets its own dedicated discussion.

@Akryum

This comment has been minimized.

Copy link
Member

Akryum commented Jan 15, 2019

Maybe the shorthand could be phased out of the proposal and we can make another RFC to discuss it?

@yyx990803

This comment has been minimized.

Copy link
Member Author

yyx990803 commented Jan 15, 2019

In response to concerns raised by @chrisvfritz regarding the original proposal:

Usage of v-bind on <slot>:

I personally don't think this is something that should be forced at the syntax level. Preventing such usage would be overly opinionated from my perspective; a note in documentation on the potential drawbacks of such usage or even a soft warning from the compiler seems more reasonable.

slot and slot-props not being directives.

This is something I also think is not fully resolved.

The reason slot was originally designed as a special attribute, was because it is a special attribute based on the Shadow DOM spec.

When introducing slot-scope, it was also a special attribute because it looks inconsistent to have a directive and a non-directive working together to denote the functionality of a single slot:

<foo>
  <template slot="foo" v-slot-scope="{ bar }>
  </template>
</foo>

Regarding the new proposal, slot-props is probably the term that best corresponds to the underlying concept: it's the props being passed to this slot. This can potentially help a lot in future education. I think slot-scope was confusing mostly because of such a lack of conceptual connection. Short directives like v-slot or v-scope also has the same problem.

We can try to make it a directive, like v-slot-props:

<foo>
  <template slot="foo" v-slot-props="{ bar }">
  </template>
</foo>

But the inconsistency still exists.

slot and slot-props being separate attributes

This is again a carry-over from existing syntax so it can be introduced on top of simple slot="foo" usage.

It is indeed a viable option to ditch the syntax similarity to Shadow DOM slots altogether, since they are quite different internally (especially with scoped slots). This gives us the opportunity to unify the slot name and slot props designation in the same syntax. The v-slot proposal is a good exploration in this area, but as I mentioned I'm afraid the amount of micro syntax is a dealbreaker.

I actually had a similar draft for unifying slot usage in a single directive:

<foo>
  <template v-slot="{ msg }">
    Default slot {{ msg }}
  </template>
  <template v-slot:one="{ msg }">
    Slot one {{ msg }}
  </template>
</foo>

Here the slot name is denoted via a directive argument (like event names for v-on).

The major drawback is the default slot usage v-slot="{ msg }" isn't as conceptually accurate as slot-props.

Also, using arugments means the syntax do not support dynamic slot names (maybe can be solve as v-slot:[one]="{ msg }"

But the big bonus is that it makes the shorthand conversion rules are now almost identical to v-on and v-bind:

  • v-bind:id="id" => :id="id"
  • v-on:click="foo" => @click="foo"
  • v-slot:one="{ msg }" => (one)="{ msg }" or &one="{ msg }"

And the shorthand syntax is exactly the same as in the original proposal.

@yyx990803

This comment has been minimized.

Copy link
Member Author

yyx990803 commented Jan 15, 2019

For reference: shorthand syntax comparison

Btw @CyberAP - [] is also valid Angular syntax.

()

<foo ()="foo">
  <bar ()="bar">
    <baz ()="baz">
      {{ foo }} {{ bar }} {{ baz }}
    </baz>
  </bar>
</foo>

<foo>
  <template (header)="{ msg }">
    Header msg: {{ msg }}
  </template>

  <template (footer)="{ msg }">
    Footer msg: {{ msg }}
  </template>
</foo>

&

<foo &="foo">
  <bar &="bar">
    <baz &="baz">
      {{ foo }} {{ bar }} {{ baz }}
    </baz>
  </bar>
</foo>

<foo>
  <template &header="{ msg }">
    Header msg: {{ msg }}
  </template>

  <template &footer="{ msg }">
    Footer msg: {{ msg }}
  </template>
</foo>

$

<foo $="foo">
  <bar $="bar">
    <baz $="baz">
      {{ foo }} {{ bar }} {{ baz }}
    </baz>
  </bar>
</foo>

<foo>
  <template $header="{ msg }">
    Header msg: {{ msg }}
  </template>

  <template $footer="{ msg }">
    Footer msg: {{ msg }}
  </template>
</foo>

*

<foo *="foo">
  <bar *="bar">
    <baz *="baz">
      {{ foo }} {{ bar }} {{ baz }}
    </baz>
  </bar>
</foo>

<foo>
  <template *header="{ msg }">
    Header msg: {{ msg }}
  </template>

  <template *footer="{ msg }">
    Footer msg: {{ msg }}
  </template>
</foo>

#

<foo #="foo">
  <bar #="bar">
    <baz #="baz">
      {{ foo }} {{ bar }} {{ baz }}
    </baz>
  </bar>
</foo>

<foo>
  <template #header="{ msg }">
    Header msg: {{ msg }}
  </template>

  <template #footer="{ msg }">
    Footer msg: {{ msg }}
  </template>
</foo>
@edimitchel

This comment has been minimized.

Copy link

edimitchel commented Jan 15, 2019

#

I really like the hash # for 2 reasons:

  • The symbol illustrates an hole (like a pipe) : scope works like multiple pipes
  • The hash means for me a targeting feature : target scope for template and target scope props.
@dima74

This comment has been minimized.

Copy link

dima74 commented Jan 15, 2019

Why not allow shorthand on normal elements? When using shorthand there is no ambiguity:

<parent>
  <foo
    (header)="parentSlotProps"
    ()="defaultSlotProps"
  >
    ...
  </foo>
</parent>
@yyx990803

This comment has been minimized.

Copy link
Member Author

yyx990803 commented Jan 15, 2019

@dima74 because then <foo ()="defaultSlotProps"> would be valid syntax for passing <foo> as the default slot to <parent>.

@yyx990803

This comment has been minimized.

Copy link
Member Author

yyx990803 commented Jan 15, 2019

Would love to get a sentiment on the general reception of the shorthand here...

Please vote:

  • 👍 for "Ok with shorthand and can live with any reasonable symbol"
  • 😕 for "Ok with shorthand but have strong opinion about which symbol to use (or not to use)"
  • 👎 for "Don't think shorthand should be added / won't use it"
@Justineo

This comment has been minimized.

Copy link
Member

Justineo commented Jan 16, 2019

If we choose to go with directives, using directive args is definitely a better choice than creating another micro syntax inside attribute values IMO. The only inconsistency here, like what @chrisvfritz proposed for multiple v-model bindings for v3, is that the usage without args doesn’t mean setting all bindings as an object like v-on and v-bind.

@dccampbell

This comment has been minimized.

Copy link

dccampbell commented Jan 16, 2019

() - I think the first couple posts by @darrenjennings and @znck cover my main concerns well. I'd add a small worry about overuse of parenthesis in general, which are valid in what feels like the vast majority of contexts. This adds one more and with a fairly unique meaning. At a glance it really looks and feels like a shorthand function, which is already a somewhat confusing new syntax for many developers (in particular those who aren't strictly JS devs and/or are new to ES6). Thinking about traditionally non-frontend devs who may be using Vue in their first Node/Webpack/Babel/etc project...they're already having to adjust to how arrow functions look and work. I wonder if this won't just add to that whiplash a bit...

& - I'd lean towards this currently, as I can't say I feel any strong associations for the symbol in this context. @darrenjennings noted concern over looking like pointers/references in other languages, but for me thinking of it that way actually made it even more attractive for this use case.

$ - I generally tend to avoid this symbol if possible purely due to historic associations/overuse in JS.

* - Non-full height characters feel...weird, IMHO. It does stand out, but leaves me kinda conflicted on it.

# - Initially didn't quite like it, and I think @edimitchel's description of it looking like a hole is...well...a bit of a stretch. That said, his second point turned me around on it some: hashes are often used for targeting something by id/name, and I kinda see that fitting the usage here. Still lean towards &, but okay w/ this.

v-slot:name (directive) - Feels simple and familiar, my only strong concern is the inconsistency @Justineo pointed out. But given that it solves some even bigger inconsistencies, may be worth that trade-off?

@dsonet

This comment has been minimized.

Copy link

dsonet commented Jan 16, 2019

Like @darrenjennings 's idea for use & as shorthand syntax.

@zfeher

This comment has been minimized.

Copy link

zfeher commented Jan 16, 2019

@yyx990803 can you add an example where both the default and name slots are used?

I guess it would look like this:

<foo>
  <template ()="one">
    {{ one }}
  </template>

  <template (two)="two">
    {{ two }}
  </template>

  <template (three)="three">
    {{ three }}
  </template>
</foo>

While trying to get to the above example I created this version too which probably a mistake and won't work:

<foo ()="one">
  <template (two)="two">
    {{ two }}
  </template>

  <template (three)="three">
    {{ three }}
  </template>
</foo>
@murr4y

This comment has been minimized.

Copy link

murr4y commented Jan 16, 2019

I agree with @dccampbell, () looks too much like a function and it's especially confusing when compared to React (as @dimensi pointed out) because it doesn't behave like a function that receives a prop.

I voted against a shorthand because not a lot of keystrokes are saved and makes it potentially more confusing for beginners (and scoped slots/'slots with props' are quite confusing to begin with).
Even if you have a shorthand that makes sense (I personally like [ ] because well, it looks like a slot!) the result may not express what's happening.

<template (two)="{ two }">
    {{ two }}
  </template>

It kind of looks like you're assigning a value to a slot but actually you receive something to use for your slot.

Conceptually it's more like

<template slot="two" from-props="{ two }">
  </template>

or like

<template { two }=slotname.props>...

slot-props makes this a little clearer I think and would help education like @yyx990803 said:

the underlying concept: it's the props being passed to this slot.

@rellect

This comment has been minimized.

Copy link

rellect commented Jan 16, 2019

@murr4y A shorthand isn't useful just for saving a few strokes, for me it helps to easily scan the code and understand what's going at a glance.

It seems this discussion is focused on the shorthand more than the actual proposal, but I think it's important and giving it its own symbol will encourage using scoped slots.

@yyx990803

This comment has been minimized.

Copy link
Member Author

yyx990803 commented Jan 16, 2019

Since we seem to have a consensus for the updated v-slot based syntax and most of the disagreements are regarding the shorthand, I'm dropping it from this RFC so we can merge this one and discuss the shorthand and dynamic keys in separate RFCs.

@znck

znck approved these changes Jan 16, 2019

@yyx990803 yyx990803 merged commit f8fc6ab into master Jan 16, 2019

@Nandiin

This comment has been minimized.

Copy link

Nandiin commented Jan 17, 2019

I'm concerning about the inconsistency between default slot and named slot introduced by this v-slot based syntax.

<foo v-slot="defaultSlotProp">
  Using {{ defaultSlotProp }} here
  <template v-slot:slotName="namedSlotProp">
    <bar>Using {{ namedSlotProp }} here</bar>
  </template>
</foo>

As we can easily see, v-slot directive for the default slot exists on the component node while that for the named slot exists on the template wrapper (one on the parent while another on the child)

IMO, this consistency is due to v-slot having two different effects/roles/responsibilities:

  1. Locates the content that should be plugged into the slot position.
  2. Provides a place to declare slot scope.

While the default slot usecase only covers the second one. (The locating is achieved by grabbing all nameless-nodes -- a convention other than v-slot)

So I think providing two different directives for these two purposes would be better:

  1. v-scope on the parent component which provides a centeral place to declare slot scope. All slot scopes, including named ones, would be declared on the parent component.
  2. v-slot acts exactly the same as the original slot (or we could leave it unchanged)

Advantages:

  1. Default slot and named slot usages are consistent.
  2. Get rid of <template> verbosity for named slots
  3. All scope declarations is on the provider node which improves clarity.

Drawbacks:

  1. Need to modify two nodes to add/remove a named slot
    But I would argue that the frequency of adding/removing slots during development is considerably low. The structure is somehow fixed when we start coding and thus we know how many slots we would put into when using a certain component and it merely changes.

Examples:

  1. default slot without scope (no changes introduced)
<foo>
    <div>I'm slot content</div>
    <div>I'm slot content, too</div>
</foo>
  1. default slot with scope
<foo v-scope:default="foo">
    <div> {{ foo }} can be used here </div>
    <div> {{ foo }} can aslo be used here </div>
</foo>
  1. default slot and named slot with scopes
<foo v-scope:default="foo" v-scope:header="headerProp">
    <div> Referencing {{ foo }} </div>
    <div v-slot="header"> Referencing {{ headerProp }}</div>
</foo>

We might elminate the necessity to explicitly write :default after v-scope when it's for default slots.

@znck

This comment has been minimized.

Copy link
Member

znck commented Jan 17, 2019

This would cause confusion as visually scoped variables are available in every slot, which is indeed wrong.

<foo v-scope:default="foo" v-scope:header="headerProp">
    <div> Referencing {{ headerProp }} </div>
    <div v-slot="header"> Referencing {{ headerProp }}</div>
</foo>
@leopiccionia

This comment has been minimized.

Copy link

leopiccionia commented Jan 17, 2019

This ship has sailed, but, in my opinion, the slot+slot-props and slot+v-scope proposals were better (I don't know if it's viable to have scope in 3.x again, but I'm OK with it being a directive) and closer to faux-HTML syntax. The resulting code was slightly more verbose than this RFC, but easier to parse (possible bias here).

Then:

<foo>
  <template slot="one" v-scope="{someProp}">
    <bar v-scope="bar">
      <div>{{ someProp }} {{ bar }}</div>
    </bar>
  </template>
</foo>

Now:

<foo>
  <template v-slot:one="{someProp}">
    <bar v-slot="bar">
      <div>{{ someProp }} {{ bar }}</div>
    </bar>
  </template>
</foo>

As a nicer sidenote, it solves the problem of dynamic slot much more elegantly.

@Nandiin

This comment has been minimized.

Copy link

Nandiin commented Jan 18, 2019

@znck Yeah, I missed that point, thanks for pointing out. While compared to intrinsic confusion caused by syntax inconsistency, this one can be easily catched by the compiler (at least at non production build) or cause a meaningful warning at runtime.

Or, we can say that referencing other slot's scope is a misuse of the API, like already existing "Property or method xxx is not defined on the instance but referenced during render" error. However, if the API itself is inconsistent at the first place, it might prevent this misuse from happening, but the price is to make every newcomer confused.

@znck

This comment has been minimized.

Copy link
Member

znck commented Jan 18, 2019

In my opinion, the API is quite consistent:

For example, the default slot usage should be:

<foo>
  <template v-slot="one">
    {{ one }}
  </template>
</foo>

But it's little verbose so the API allows putting the default slot scope on the component tag itself.

<foo v-slot="one">
  {{ one }}
</foo>

But if there is one more named slot then API forces to put the default slot as a child with a template wrapper:

<foo>
  <template v-slot="one">
    {{ one }}
  </template>

  <template v-slot:two="two">
    {{ two }}
  </template>

  <template v-slot:three="three">
    {{ three }}
  </template>
</foo>
@Nandiin

This comment has been minimized.

Copy link

Nandiin commented Jan 18, 2019

But if there is one more named slot then API forces to put the default slot as a child with a template wrapper

If that constraint exists, then the consistency problem is solved. I'm sorry that I haven't preceived that idea from the proposal.

However, another motivation (quoted below) of the proposal is somehow compromised

the placement of slot-scope doesn't always clearly reflect which component is actually providing the scope variable.

The only case where the placement of new v-slot reflects which component is providing the scope variable is the simplified less-verbose default-slot-only case.

I understand that not clearly reflecting which component is actual provider is only a visual issue and does not lead to any compiler ambiguity, but the visual issue itself do be among the original motivations.

Example (stolen from the proposal):

<!-- old nested usage -->
<foo>
  <bar slot-scope="foo">
    <baz slot-scope="bar">
      <div slot-scope="baz">
        {{ foo }} {{ bar }} {{ baz }}
      </div>
    </baz>
  </bar>
</foo>
<!-- The proposal considers above code as bad -->
<!-- and states that the new syntax improves the clarity as below  -->
<foo v-slot="foo">
  <bar v-slot="bar">
    <baz v-slot="baz">
      {{ foo }} {{ bar }} {{ baz }}
    </baz>
  </bar>
</foo>


<!-- But when we bring some named slots and the constraint -->

<!-- OLD -->
<foo>
  <bar slot-scope="foo">
    <baz slot-scope="bar">
      <div slot-scope="baz">
        {{ foo }} {{ bar }} {{ baz }}
      </div>
      <template slot="named-slot-for-baz">BAZ</template>
    </baz>
    <template slot="named-slot-for-bar">BAR</template>
  </bar>
  <template slot="named-slot-for-foo">FOO</template>
</foo>

<!-- NEW -->
<foo>
  <template v-slot="foo">
    <bar>
      <template v-slot="bar">
        <baz>
          <template v-slot="baz">
            <div>
              {{ foo }} {{ bar }} {{ baz }}
            </div>
          </template>
         <template v-slot:named-slot-for-baz>BAZ</template>
       </baz>
     </template>
     <template v-slot:named-slot-for-bar>BAR</template>
    </bar>
  </template>
  <template v-slot:named-slot-for-foo>FOO</template>
</foo>

<!-- This seems bringing nothing but verbosity to me -->


<!-- v-scope + slot approach -->
<foo v-scope:default="foo">
  <bar v-scope:default="bar">
    <baz v-scope:default="baz">
      <div>
        {{ foo }} {{ bar }} {{ baz }}
      </div>
      <template slot="named-slot-for-baz">BAZ</template>
    <baz>
    <template slot="named-slot-for-bar">BAR</template>
  <bar>
  <template slot="named-slot-for-foo">FOO</template>
</foo>

As I pointed out in my first comment, the root of the problem is v-slot doing 2 different things and these 2 things contradict each other under the condition of placement of v-slot should reflect which component actually provides the scope variable:

  • To make the v-slot placement reflects the provider, it should be on the provider
  • To make the v-slot placement determines the node(s) that should be plugged into slot, it should be on the child (template wrapper).
@Nandiin

This comment has been minimized.

Copy link

Nandiin commented Jan 18, 2019

As a second thought, @zchk's concern is about another motivation that I may state as

Placement of scope declaration should be as close to the actual consumer node as possible.

which seems impossible to be compatible with

Placement of scope declaration should reflect component actually provides the scope variable. (I substituted v-slot with scope declaration for clarity)

This makes the problem a simple trade-off decision and the rest is up to the core team to make the call.

@yyx990803

This comment has been minimized.

Copy link
Member Author

yyx990803 commented Jan 19, 2019

@Nandiin your example of nested default slots vs. nested named slots is a considered tradeoff, because the former is in practice more commonly seen than the latter. I'd also argue the new syntax in this case, while being more verbose, is easier to read and understand.

@smolinari

This comment has been minimized.

Copy link

smolinari commented Jan 19, 2019

I've taken some code from a tutorial on using dynamic named slots I was working on and would like to rebuild it with the new proposal to see if I understand it.

The idea of the code below is to build different types of survey questions using the dynamic named slots feature. It is also using Quasar Framework btw.

<!-- old -->
<!-- this is the questions component code -->
<template id="questions-template">
  <div>
    <div v-for="question in questions" :key="question.id"> 
      <slot name="text" :text="question.text"></slot>
      <slot :name="question.id" :input="question.input"></slot>
    </div>
  </div>
</template>

<div id="q-app">
  <p class="caption"><strong>{{ title }}</strong></p>
  <questions-component :questions="questions">
    <div slot="text" slot-scope="{ text }">
      {{ text }}
    </div>    
    <template v-for="question in questions"
      :key="question.id"
      :slot="question.id" 
      slot-scope="{ input }"
    >
      <q-field :helper="input.help">
        <component v-if="input.type === 'input'"
          is="q-input"
          v-model="input.value">
        </component> 
        <component v-if="input.type === 'select'"
          is="q-select"
          v-model="input.selection"
          :options="input.options">
        </component> 
        <component v-if="input.type === 'multi-select'"
          is="q-select"
          v-model="input.selection"
          multiple
          :options="input.options">
        </component> 
      </q-field><br/>
    </template>        
  </questions-component>
</div>
<!-- New -->
<!-- this is the questions component code -->
<template id="questions-template">
  <div>
    <div v-for="question in questions" :key="question.id"> 
      <slot name="text" :text="question.text"></slot>
      <slot :name="question.id" :input="question.input"></slot>
    </div>
  </div>
</template>

<div id="q-app">
  <p class="caption"><strong>{{ title }}</strong></p>
  <questions-component :questions="questions">
    <div v-slot:text="{ text }">
      {{ text }}
    </div>    
    <template 
      v-for="question in questions"
      :key="question.id"
      v-slot:[question.id]="{ input }"
    >
      <q-field :helper="input.help">
        <component v-if="input.type === 'input'"
          is="q-input"
          v-model="input.value">
        </component> 
        <component v-if="input.type === 'select'"
          is="q-select"
          v-model="input.selection"
          :options="input.options">
        </component> 
        <component v-if="input.type === 'multi-select'"
          is="q-select"
          v-model="input.selection"
          multiple
          :options="input.options">
        </component> 
      </q-field><br/>
    </template>        
  </questions-component>
</div>

Is this correct?

Dynamic slot names weren't mentioned in this RFP, but would that be how they might work? ( and please do keep them! 😃)

Scott

@Akryum

This comment has been minimized.

Copy link
Member

Akryum commented Jan 19, 2019

@smolinari

This comment has been minimized.

Copy link

smolinari commented Jan 19, 2019

@Akryum - Thanks. I missed that RFC completely. 😊 But, it seems I got it right. 😁

Scott

@yyx990803

This comment has been minimized.

Copy link
Member Author

yyx990803 commented Jan 19, 2019

@smolinari the new syntax requires <template> wrapper for named slots, so for your first div it has to be <template v-slot:text="{ text }"><div>{{ text }}</div></template>

@smolinari

This comment has been minimized.

Copy link

smolinari commented Jan 19, 2019

Aha! Ok. Thanks for the clarification Evan.

Scott

@guilhermeaiolfi

This comment has been minimized.

Copy link

guilhermeaiolfi commented Jan 22, 2019

Am I late to the party?

Can't the v-slot be used to represent the name? like:

Named Slots: <template v-slot="namedSlot">
Default slot: <template v-slot>

And use attributes to get the variables into the slot scope:
<template v-slot :someProp="someProp">{{someProp}}</template>

For scoped slots (just like in <style scoped>):
<template v-slot :onlyProp="someVar" scoped>{{onlyProp}}</template>

@donnysim

This comment has been minimized.

Copy link

donnysim commented Jan 24, 2019

Would it be possible to output the same slot to multiple named slots? For example the select component provides customization for dropdown item and displayed selected item via different slots, and the developer wants to use the same template for both?

@JosephSilber

This comment has been minimized.

Copy link

JosephSilber commented Jan 24, 2019

@donnysim great idea 👍

I've had that usecase come up before, and had to resort to create a local component to contain that.

For those trying to understand the use-case better: take a look at select2's documentation: http://select2.github.io/select2/

46a452a2-e827-4930-bcb7-4f0dc9aea431

Could we maybe allow two of them on the same element?

<beefy-select>
    <div #selection="item" #result="item"></div>
<beefy-select>

And then behind the scenes, the compiler would assign that to both functions.

@smolinari

This comment has been minimized.

Copy link

smolinari commented Jan 24, 2019

@JosephSilber - just remember, with this change, named slots must be within a <template> (again). 😃

Maybe it could work this way too???

<beefy-select>
    <template #[beefySelectSlots]="item">
      <div></div>
    </template>
<beefy-select>

and in the code

export default {
  data () {
    return {
      beefySelectSlots: ['selection', 'result'],
....

In other words, if the slot's name argument is just a string, there is only one slot to fill. If it's a dynamic named slot and the argument is an array, then there are multiple slots to fill. That would make this really flexible. Adds to the complexity though. 😄

Scott

@donnysim

This comment has been minimized.

Copy link

donnysim commented Jan 24, 2019

But then inline would look like

<beefy-select>
    <template #[['selection', 'result']]="item">
      <div></div>
    </template>
<beefy-select>

? I don't really like having to declare a variable just to pass multiple slots, it just feels disconnected when viewing the template.

I'm hoping that at least this:

<beefy-select>
    <template v-slot:selection="item" v-slot:result="item">
      <div></div>
    </template>
<beefy-select>

is feasible or any other syntax, as long as it makes it possible without having to leave the template.

@smolinari

This comment has been minimized.

Copy link

smolinari commented Jan 24, 2019

@donnysim

I don't really like having to declare a variable just to pass multiple slots, it just feels disconnected when viewing the template.

Well, I can't disagree with you and in fact, I was going to ask if this might be possible.

#['selection', 'result']="item"

But, I seemed to remember reading somewhere that spaces in the argument like that wouldn't be possible. Thus, why I suggested the variable/ array. I guess we'll have to wait until Evan can chime in on this.

Scott

@donnysim

This comment has been minimized.

Copy link

donnysim commented Jan 24, 2019

@smolinari oh yeah, true, true, I totally forgot about spaces.

@yyx990803 yyx990803 changed the title Proposal for new scoped slots syntax Proposal for new slots syntax Jan 30, 2019

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment