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

feat(core): support passing down scopedSlots with v-bind #7765

Closed
wants to merge 2 commits into from

Conversation

yyx990803
Copy link
Member

Allow passing down scoped slots using v-bind:

<child v-bind="{ scopedSlots: $scopedSlots }">
</child>

close #7178, close #7351

@yyx990803
Copy link
Member Author

Btw, this is still open to discussion, since the syntax isn't entirely consistent.

You can do v-bind="{ ref: 'foo', slot: 'bar' }" which makes sense because ref and slot can be bound individually as attributes, but scopedSlots is not.

Internally ref and slot are treated specially because they are "reserved" attributes and thus merged into the VNode's data as a root-level property.

So alternatively, we can expose a special convention for v-bind: if a property starts with $, it will always be merged as a root-level property into the VNode's data.

This then allows the usage:

<child v-bind="{ $scopedSlots } />

In addition, if you know the underlying VNode data interface, you can also do low-level stuff without dropping into render functions:

<child v-bind="{
  $on: {
    someEvent: someMethod
  },
  $nativeOn: {
    click: someMethod
  }
}"/>

@ktsn
Copy link
Member

ktsn commented Mar 14, 2018

What about adding a dedicated directive for merging vnode data? Since v-bind is used to be used for passing parent component data to child component attribute/props, it is a bit weird if it can modify underlying vnode data, IMHO.

If we have such directive we can simply write the vnode data as the same shape as in render function (named it v-vnode as a placeholder 😉 ):

<child v-vnode="{
  scopedSlots: $scopedSlots,
  on: {
    someEvent: someMethod
  },
  nativeOn: {
    click: someMethod
  }
}"/>

We may write directive argument like v-bind:

<child v-vnode:scoped-slots="$scopedSlots" />

This also has a benefit that we may able to write template type checker for that as we can simply utilize Vue's typescript typings. (FYI: The current WIP implementation of template type checker is here)

@dennythecoder
Copy link

The specific directive proposed by @ktsn seems a little awkward to me, but I think it would make sense to separate the behavior to improve readability.

@bigianb
Copy link

bigianb commented May 22, 2018

What's the status of this?

@kellym
Copy link

kellym commented Jun 7, 2018

So is there a consensus on using a different tag name or not? If so, what should it be called?

Otherwise, this is clearly a hangup for a lot of people using Vue (googling led me to 5-6 different issues on the same topic) and it'd be nice to either give this a thumbs up or have some direction in what needs to be done to get this shipped.

As an added note, the current "solution" of using a render function is pretty silly, especially when templates get complex and/or are already written and are getting new components added to them.

@ClickerMonkey
Copy link

ClickerMonkey commented Jun 13, 2018

I wouldn't recommend this solution, but I needed this functionality now - so I implemented @yyx990803 's change as a plugin:

var BindScopedSlotsPlugin = {
  install: function(Vue, options) {
    Vue.prototype._b = function bindObjectProps (
      data,
      tag,
      value,
      asProp,
      isSync
    ) {
      if (value) {
        if (value === null || typeof value !== 'object') {
          warn(
            'v-bind without argument expects an Object or Array value',
            this
          );
        } else {
          if (Array.isArray(value)) {
            var res = {};
            for (var i = 0; i < value.length; i++) {
              if (value[i]) {
                Vue.util.extend(res, value[i]);
              }
            }
            value = res;
          }
          var hash;
          var loop = function ( key ) {
            if (
              key === 'class' ||
              key === 'style' ||
              key === 'scopedSlots' || // new code
              Vue.config.isReservedAttr(key)
            ) {
              hash = data;
            } else {
              var type = data.attrs && data.attrs.type;
              hash = asProp || Vue.config.mustUseProp(tag, type, key)
                ? data.domProps || (data.domProps = {})
                : data.attrs || (data.attrs = {});
            }
            if (!(key in hash)) {
              hash[key] = value[key];

              if (isSync) {
                var on = data.on || (data.on = {});
                on[("update:" + key)] = function ($event) {
                  value[key] = $event;
                };
              }
            }
          };

          for (var key in value) loop( key );
        }
      }
      return data;
    };
  }
};

Vue.use( BindScopedSlotsPlugin );

The syntax is as mentioned initially: v-bind="{ scopedSlots: $scopedSlots }"

@joernroeder
Copy link

@ClickerMonkey thanks for building the plugin! there is a tiny typo: if (arr[i]) needs to be replaced with if (value[i])

@ClickerMonkey
Copy link

Thanks! I have a smaller simpler version:

    Vue.prototype._b = (function (bind) {
      return function(data, tag, value, asProp, isSync) {
        if (value && value.$scopedSlots) {
          data.scopedSlots = value.$scopedSlots;
          delete value.$scopedSlots;
        }
        return bind.apply(this, arguments);
      };
    })(Vue.prototype._b);

And you just need to add v-bind="{$scopedSlots}" to the component.

@RedShift1
Copy link

Check out this solution too: https://stackoverflow.com/a/52823029/2050460

Added another test to ensure default slot contents are preserved as well as slots passed to children.
@ggirodda
Copy link

I edited the @ClickerMonkey script to merge the parent and the current $scopedSlots, maybe it can be useful for someone

import Vue from "vue"

Vue.prototype._b = (function(bind) {
  return function(data, tag, value, asProp, isSync) {
    if (value && value.$scopedSlots) {
      data.scopedSlots = {
        ...data.scopedSlots,
        ...value.$scopedSlots
      }
      delete value.$scopedSlots
    }
    return bind.apply(this, arguments)
  }
})(Vue.prototype._b)

@yyx990803
Copy link
Member Author

So I don't think we will land this, because:

  1. I'm not entirely satisfied with the consistency of the change. Feels like a dirty special case.

  2. There is already a way to make it work in userland.

  3. This is probably the most important reason: we want to avoid landing changes in v2 that would then have to be broken in v3. In v3, slots and scoped slots are being unified under $slots, and slots itself is becoming a reserved prop like key and ref. This means in v3 you can just do:

<foo :slots="$slots"></foo>

Moreover, we have other changes in v3 that makes passing things down to child components easier (RFC in early 2019).

@yyx990803 yyx990803 closed this Dec 28, 2018
@ClickerMonkey
Copy link

Glad to hear about v3. Thanks Evan for everything!

serebrov added a commit to serebrov/emoji-mart-vue that referenced this pull request Feb 28, 2019
Started with the granular slots (inside the Search / Preview
components), but it appears that fallback content is not rendered
in the nested slots in vue 2.5.
It works in 2.6 and should be further improved in 3.0,
see vuejs/vue#7765 (comment)
KaelWD added a commit to vuetifyjs/vuetify that referenced this pull request Jun 10, 2019
@shameleo
Copy link

shameleo commented Oct 29, 2020

3. In v3, slots and scoped slots are being unified under $slots, and slots itself is becoming a reserved prop like key and ref. This means in v3 you can just do:

<foo :slots="$slots"></foo>

So, there is no reserved slot prop in Vue 3. Any new ways to pass down scoped slots are available? Or planned? Or we still need old workarounds like <template> / <slot> pair?
Ahh, I see: vuejs/core#575

@yyx990803 yyx990803 deleted the feat-scoped-slot-passdown branch May 20, 2022 06:40
meticulousfan added a commit to meticulousfan/vue-emoji-mart that referenced this pull request Aug 17, 2022
Started with the granular slots (inside the Search / Preview
components), but it appears that fallback content is not rendered
in the nested slots in vue 2.5.
It works in 2.6 and should be further improved in 3.0,
see vuejs/vue#7765 (comment)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Ability to pass scoped slots to descedants in templates
10 participants