Vue: $slots include empty vnodes

Created on 30 Mar 2017  ·  4Comments  ·  Source: vuejs/vue

Version

2.2.6

Reproduction link

https://jsbin.com/mugosexini/edit?html,css,js,output

Steps to reproduce

const child = {
    name: "child",
    render: function () {
            console.log(this.$slots.default)
            return 1
    }
}

const parent = {
    template: "<div>\
                             <child>\
                               <p>1</p>\
                                   <p>2</p>\
                            </child>\
                         </div>",
    components: {
           child: child
        }
}

the child component console the $slots.default is a vnodes list which length is 3.

It also reproduce when I wrote parent template in <template> element

What is expected?

this.$slots.default DO NOT INCLUDE empty vnodes

What is actually happening?

this.$slots.default include a empty vnode between two <p> element


this.$slots.default seems different from React's this.props.children...

Most helpful comment

Please consider mentioning this in the docs somewhere, because people normally do not expect this behavior. I personally for one spent considerable time just to find out here that this is what is causing my code to deviate from expected behavior. Thanks.

All 4 comments

This is expected. Vue preserves whitespaces between elements unless you use vue-loader with preserveWhitespace: false.

Please consider mentioning this in the docs somewhere, because people normally do not expect this behavior. I personally for one spent considerable time just to find out here that this is what is causing my code to deviate from expected behavior. Thanks.

Ditto that. This behavior is quite confusing, especially when we need to determine if a slot is simply receiving empty strings. In this example, you can see that the slot is being rendered even thought it receives nothing: http://jsfiddle.net/teddyrised/p07fgw54/3/

Given the following template:

<div id="root">
  <custom desc="Slot with string"><template slot="test-slot">aaa</template></custom>
  <custom desc="Slot with `data` defined content"><template slot="test-slot">{{ this.emptyContent }}</template></custom>
  <custom desc="Slot with `data` defined empty content"><template slot="test-slot">{{ this.emptyContent }}</template></custom>
  <custom desc="Slot that is empty"><template slot="test-slot"></template></custom>
  <custom desc="No slot defined"></custom>
</div>

<template id="template">
  <div>
    <span>Always here.</span>
    <strong v-if="hasSlotData">
        Displayed only when slot passed: <slot name="test-slot"></slot>. 
    </strong>
    <span>{{ this.desc }}</span>
  </div>
</template>

And the following JS setup:

Vue.component('Custom', {
  template: '#template',
  props: {
    desc: String
  },
  computed: {
    hasSlotData() {
        return this.$slots['test-slot'];
      },
      hasSlot() {
        return true;
      }
  }
});

new Vue({
  el: '#root',
  data: {
    content: 'Has random content',
    emptyContent: ''
  }
});

I would expect the last 3 <custom> elements to not render the <strong> element at all, but it does not work as intended.

好吧... 我也觉得这点应该提一下 不然有点坑

Was this page helpful?
0 / 5 - 0 ratings