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

Proposal for new slots syntax #2

Merged
merged 8 commits into from Jan 16, 2019
Merged

Proposal for new slots syntax #2

merged 8 commits into from Jan 16, 2019

Conversation

yyx990803
Copy link
Member

@yyx990803 yyx990803 commented Jan 14, 2019

@znck
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
Copy link
Member

znck commented Jan 14, 2019

Is this valid syntax?

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

@yyx990803
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
Copy link

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
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
Copy link

@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
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
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
Copy link
Contributor

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
Copy link
Contributor

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
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
Copy link
Contributor

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
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
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
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
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
Copy link

#

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
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
Copy link
Member Author

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

@yyx990803
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
Copy link
Member

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
Copy link

() - 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
Copy link

dsonet commented Jan 16, 2019

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

@zfeher
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
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.

@4refael
Copy link

4refael 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
Copy link
Member Author

@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
Copy link
Contributor

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
Copy link
Member

Akryum commented Jan 19, 2019

@smolinari See #4

@smolinari
Copy link
Contributor

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

Scott

@yyx990803
Copy link
Member Author

@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
Copy link
Contributor

Aha! Ok. Thanks for the clarification Evan.

Scott

@guilhermeaiolfi
Copy link

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
Copy link

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
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
Copy link
Contributor

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
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
Copy link
Contributor

@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
Copy link

@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
@pxwee5

This comment has been minimized.

@yyx990803
Copy link
Member Author

@pxwee5 this is an implementation bug, please open an issue in the core vue repo instead of commenting on the RFC.

@pxwee5
Copy link

pxwee5 commented Jun 19, 2019

Was clarifying if it's a bug or not.
Issue created by MyBeta vuejs/vue#10152
Push requested by zrh122 vuejs/vue#10167

@Raiondesu
Copy link

Raiondesu commented Jul 1, 2019

@smolinari, your proposal, unfortunately, would break all compatibility with pug (see this part), the loader for which (pug-loader) already struggles with the current shorthand syntax (#name="boundValue").

@tmorehouse
Copy link

tmorehouse commented Aug 14, 2019

It is too bad (that I am too late in bringing this up, as it wasn't until trying out the new syntax), but there are issues (most likely previously discussed elsewhere) with putting slot names/vars in the attribute name.

A few key issues are (that really limit the flexibility of scoped slots with the new syntax):

  1. case sensitivity of slot names is lost when using in-document templates: browsers will always lowercase the attribute name (but will leave the content between the double quotes as-is) when the browser parses the DOM template

  2. Slot names can no longer have spaces in them (unless passed as a variable), nor can they start with a square bracket [

  3. for dynamic slot names (using the square bracket syntax), one cannot always use a camelCase variable name if using in-browser templates... the var name will be lower cased by the browser (see point #1) and the slot name will be passed as undefined to v-slot (as the variable does not exist, and will probably cause an error/warning to be thrown)

  4. Inline JavaScript evaluation to handle, say special chars in a slot name (rather than creating a computed prop or data to store the slot name), won't work when used in browser template: v-slot:[`'[foo]'`]="scope" as single quotes (') are invalid attribute name characters.

  5. The current v-slot implementation puts users who are using in-browser templates at a disadvantage over users of vue-loader/SFC due to the above "gotchas" with case sensitivity on the v-slot attribute name syntax (and there is no obvious mention of this limitation in the docs). One of the key selling points of Vue is that it can be used without a module bundler for those starting out, or those who just want to add a few Vue components to their existing web sites. Users may run into these "bugs" (gotchas) and spend hours debugging.

  6. While the current v-slot syntax looks like a directive with a value expression, it is not really the same as any other directive that accepts an expression as it is declaring variable names — which one normally can't do with any other directive (i.e. getting values returned back from a directive). This leads me to believe that the 2.6+ template compiler has to have special handling just for v-slot to deal with declaring the scope variable names.

A better approach (in my opinion), would have been to break the new syntax in two:

<!-- pass a string literal as the slot name (needs quotes, just like regular directives) -->
<!-- preserves case, spacing, special chars, etc, in slot name -->
<template v-slot="'slotName'" v-scope="{ some, props, here }"></template>

<!-- pass a variable containing the slot name, case sensitivity of the variable is preserved -->
<template v-slot="someVar" v-scope="{ some, props, here }"></template>

v-slot would truly work like a directive with an expression in the value.

v-scope would be optional if users don't need scoping, or don't need to use the scope properties. e.g. if the scope is just passing convenience methods as properties (which could optionally be bound to event handlers on child elements), and are not needed by the user in some cases. Although this looks like a directive, it doesn't work like one as one is declaring variables rather than providing variables to the directive)

This gets around all the limitations with attribute names (spaces, case sensitivity not guaranteed, etc), while keeping the attributes as custom attributes (i.e. hyphenated, prefixed with v-).

A shortcut syntax could still be employed, but may not be so short... maybe the shortcut could be as is in 2.6+: combining both slot name and scope into a single attribute/value pair #slotname="scope" or #slotname="{ some, props, here }" for convenience, while the above suggested longhand syntax (v-slot + v-scope) provides the most flexibility to get around the limitations of attribute names if the user needs to.

The other option is to not to deprecate the old slot="slotName" slot-scope="scope" syntax... or introduce a new prop slot-name="slotName" (to complement slot-props) to replace the deprecated slot="slotname" to make sure the attribute doesn't conflict with standard single word attributes.

Another alternative would have been to create a special Vue built-in component (say <slot-target>) that accepts props such as name="slot-name" and props="{ some, name }". rather than using a directive-like syntax.

Summary:

The current 2.6+ v-slot syntax is truly limiting (in my opinion), and somewhat a step backwards with flexibility in favour of a (somewhat) shorter syntax (and is not really like a directive due to declaration of variable names in the value expression), and it works differently when using in browser x/dom templates vs module bunders with vue-loader.

cc/ @yyx990803 @posva

@viT-1
Copy link

viT-1 commented Aug 29, 2019

Where I can find official specification about new v-slot naming convention (e.g. for naming with more than one word)? And why new specification (v-slot:slotname) is not compliant to xhtml (v-slot="slotname")?

posva pushed a commit that referenced this pull request Apr 14, 2020
posva added a commit that referenced this pull request Apr 14, 2020
* Dynamic Routing

* precision from

* add getRoutes

* add alternative for getRoutes

* fix: typo in 0000-router-dynamic-routing.md (#1)

* fix typo (#2)

* Update 0000-router-dynamic-routing.md (#3)

Just some grammatical corrections. Hope you don't mind.

Scott

* remove template drawbacks

* update

* rename Symbol to symbol, type typo

* rename

Co-authored-by: pierresaid <said.pierre.emler@gmail.com>
Co-authored-by: atilkan <emrahatilkan@gmail.com>
Co-authored-by: Scott <smolinari@users.noreply.github.com>
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

Successfully merging this pull request may close these issues.

None yet