Only show slot if it has content

后端 未结 6 1328
感情败类
感情败类 2021-01-30 20:00

Is there a way to only display a slot if it has any content?

For example, I\'m building a simple Card.vue component, and I only want the footer displayed if

相关标签:
6条回答
  • 2021-01-30 20:08

    Initially I thought https://stackoverflow.com/a/50096300/752916 was working, but I had to expand on it a bit since $scopeSlots returns a function which is always truthy regardless of its return value. This is my solution, though I've come to the conclusion that the real answer to this question is "doing this is an antipattern and you should avoid it if possible". E.g. just make a separate footer component that could be slotted in.

    Hacky solution

       hasFooterSlot() {
            const ss = this.$scopedSlots;
            const footerNodes = ss && ss.footer && ss.footer();
            return footerNodes && footerNodes.length;
       }
    

    Best Practice (helper component for footer)

    const panelComponent = {
      template: `
        <div class="nice-panel">
            <div class="nice-panel-content">
              <!-- Slot for main content -->
              <slot />
            </div>
            <!-- Slot for optional footer -->
            <slot name="footer"></slot>
        </div>
      `
    }
    const footerComponent = {
      template: `
        <div class="nice-panel-footer">
            <slot />
        </div>
      `
    }
    
    var app = new Vue({
      el: '#app',
      components: {
        panelComponent,
        footerComponent
      },
      data() {
        return {
          name: 'Vue'
        }
      }
    })
    .nice-panel {
      max-width: 200px;
      border: 1px solid lightgray;
    }
    
    .nice-panel-content {
      padding: 30px;
    }
    
    .nice-panel-footer {
      background-color: lightgray;
      padding: 5px 30px;
      text-align: center;
    }
    <script src="https://unpkg.com/vue@2.6.11/dist/vue.min.js"></script>
    <div id="app">
      <h1>Panel with footer</h1>
    
      <panel-component>
        lorem ipsum
        <template #footer>
          <footer-component> Some Footer Content</footer-component>
        </template>
      </panel-component>
    
      <h1>Panel without footer</h1>
    
      <panel-component>
        lorem ipsum
      </panel-component>
    </div>

    0 讨论(0)
  • 2021-01-30 20:11

    You should check vm.$slots and also vm.$scopedSlots for it.

    hasSlot (name = 'default') {
       return !!this.$slots[ name ] || !!this.$scopedSlots[ name ];
    }
    
    0 讨论(0)
  • 2021-01-30 20:22

    In short do this in inline:

    <template lang="pug">
      div
        h2(v-if="$slots.title")
          slot(name="title")
        h3(v-if="$slots['sub-title']")
          slot(name="sub-title")
    </template>
    
    0 讨论(0)
  • 2021-01-30 20:25

    Hope I understand this right. Why not using a <template> tag, which is not rendered, if the slot is empty.

    <slot name="foo"></slot>
    

    Use it like this:

    <template slot="foo">
       ...
    </template>
    
    0 讨论(0)
  • 2021-01-30 20:30

    I have ran into a similiar issue but across a wide code base and when creating atomic design structured components it can be tiring writing hasSlot() methods all the time and when it comes to TDD - its one more method to test... Saying that, you can always put the raw logic in a v-if but i have found that the template end up cluttered and harder to read on occasions especially for a new dev checking out the code structure.

    I was tasked to find out a way of removing parent divs of slots when the slot isnt provided.

    Issue:

    <template>
      <div>
        <div class="hello">
          <slot name="foo" />
        </div>
        <div class="world">
          <slot name="bar" />
        </div>
      </div>
    </template>
    
    //instantiation
    <my-component>
      <span slot="foo">show me</span>
    </my-component>
    
    //renders
    <div>
      <div class="hello">
        <span slot="foo">show me</span>
      </div>
      <div class="world"></div>
    </div>

    as you can see, the issue is that i have an almost 'trailing' div, that could provide styling issues when the component author decides there is no need for a bar slot.

    ofcourse we could go <div v-if="$slots.bar">...</div> or <div v-if="hasBar()">...</div> etc but like i said - that can get tiresome and eventually end up harder to read.

    Solution

    My solution was to make a generic slot component that just rendered out a slot with a surrounding div...see below.

    //slot component
    <template>
      <div v-if="!!$slots.default">
        <slot />
      </div>
    </template>
    
    
    //usage within <my-component/>
    <template>
      <div>
        <slot-component class="hello">
          <slot name="foo"/>
        </slot-component>
        <slot-component class="world">
          <slot name="bar"/>
        </slot-component>
      </div>
    </template>
    
    //instantiation
    <my-component>
      <span slot="foo">show me</span>
    </my-component>
    
    //renders
    <div>
      <div class="hello">
        <span>show me</span>
      </div>
    </div>

    I came into use-case issues when trying this idea and sometimes it was my markup structure that needed to change for the benefit of this approach. This approach reduces the need for small slot checks within each component template. i suppose you could see the component as a <conditional-div /> component...

    It is also worth noting that applying attributes to the slot-component instantiation (<slot-component class="myClass" data-random="randomshjhsa" />) is fine as the attributes trickle into the containing div of the slot-component template.

    Hope this helps.

    UPDATE I wrote a plugin for this so the need for importing the custom-slot component in each consumer component is not needed anymore and you will only have to write Vue.use(SlotPlugin) in your main.js instantiation. (see below)

    const SLOT_COMPONENT = {
      name: 'custom-slot',
      template: `
        <div v-if="$slots.default">
          <slot />
        </div>
      `
    }
    
    const SLOT_PLUGIN = {
      install (Vue) {
        Vue.component(SLOT_COMPONENT.name, SLOT_COMPONENT)
      }
    }
    
    export default SLOT_PLUGIN
    
    //main.js
    import SlotPlugin from 'path/to/plugin'
    Vue.use(SlotPlugin)
    //...rest of code

    0 讨论(0)
  • 2021-01-30 20:32

    It should be available at

    this.$slots.footer
    

    So, this should work.

    hasFooterSlot() {
      return !!this.$slots.footer
    }
    

    Example.

    0 讨论(0)
提交回复
热议问题