仅在显示内容时显示插槽

时间:2017-05-19 19:02:08

标签: vue.js vuejs2

如果有任何内容,是否只能显示一个插槽?

例如,我正在构建一个简单的Card.vue组件,如果页脚插槽有内容,我只想显示页脚:

模板:

<template>

    <div class="panel" :class="panelType">

        <div class="panel-heading">
            <h3 class="panel-title">
                <slot name="title">
                    Default Title
                </slot>
            </h3>
        </div>

        <div class="panel-body">
            <slot name="body"></slot>

            <p class="category">
                <slot name="category"></slot>
            </p>
        </div>

        <div class="panel-footer" v-if="hasFooterSlot">
            <slot name="footer"></slot>
        </div>

    </div>

</template>

脚本:

<script>
    export default {
        props: {
            active: true,
            type: {
                type: String,
                default: 'default',
            },
        },

        computed: {
            panelType() {
                return `panel-${this.type}`;
            },

            hasFooterSlot() {
                return this.$slots['footer']
            }
        }
    }
</script>

在视图中:

<card type="success"></card>

由于上面的组件不包含页脚,因此不应该呈现它,但它是。

我尝试过使用this.$slots['footer'],但这会返回undefined。

有没有人有任何提示?

8 个答案:

答案 0 :(得分:23)

应该可以在

找到
this.$slots.footer

所以,这应该有用。

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

Example

答案 1 :(得分:6)

您应该检查vm.$slots以及vm.$scopedSlots

hasSlot (name = 'default') {
   return !!this.$slots[ name ] || !!this.$scopedSlots[ name ];
}

答案 2 :(得分:0)

我遇到了一个类似的问题,但是在广泛的代码库中,当创建原子设计结构化组件时,它总是很难写hasSlot()方法,而且当涉及到TDD时 - 它还有一种测试方法...说,你总是可以将原始逻辑放在v-if中,但我发现模板最终变得杂乱无章,特别是对于新的开发人员检查代码结构。

我的任务是找到一种方法,在没有提供插槽的情况下删除插槽的父div

<强>问题:

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

正如您所看到的,问题是我有一个几乎'尾随'的div,当组件作者决定不需要bar插槽时,它可能会提供样式问题。

当然,我们可以去<div v-if="$slots.bar">...</div><div v-if="hasBar()">...</div>等,但就像我说的那样 - 这可能会让人感到厌倦,并最终难以阅读。

<强>解决方案

我的解决方案是创建一个通用的slot组件,它只是渲染出一个带有周围div的插槽...见下文。

//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>

我在尝试这个想法时遇到了用例问题,有时我的标记结构需要为了这种方法的利益而改变。 这种方法减少了每个组件模板中小插槽检查的需要。我想你可以将组件看作<conditional-div />组件......

值得注意的是,将属性应用于slot-component实例化(<slot-component class="myClass" data-random="randomshjhsa" />)是正常的,因为属性会渗透到slot-component模板的包含div中。

希望这有帮助。

<强>更新 我为此编写了一个插件,因此不再需要在每个使用者组件中导入custom-slot组件,您只需在main.js实例中编写Vue.use(SlotPlugin)。 (见下文)

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

答案 3 :(得分:0)

希望我明白这一点。如果插槽为空,为什么不使用未呈现的<template>标记。

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

像这样使用:

<template slot="foo">
   ...
</template>

答案 4 :(得分:0)

简而言之,可以内联:

<template lang="pug">
  div
    h2(v-if="$slots.title")
      slot(name="title")
    h3(v-if="$slots['sub-title']")
      slot(name="sub-title")
</template>

答案 5 :(得分:0)

最初我以为https://stackoverflow.com/a/50096300/752916可以正常工作,但是由于$ scopeSlots返回的函数始终是真实的,无论其返回值如何,我都不得不对其进行扩展。这是我的解决方案,尽管我得出的结论是,此问题的真正答案是“这样做是一种反模式,如果可能,应避免使用它”。例如。只需制作一个可以插入其中的单独的页脚组件即可。

棘手的解决方案

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

最佳实践(页脚的帮助组件)

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>

答案 6 :(得分:0)

CSS 简化了很多。只需使用以下代码,瞧!

.panel-footer:empty {
  display: none;
}

答案 7 :(得分:0)

这是 Vue 3 组合 API 的解决方案:

<template>
    <div class="md:grid md:grid-cols-5 md:gap-6">

        <!-- Here, you hide the wrapper if there is no used slot or empty --> 
        <div class="md:col-span-2" v-if="hasTitle">
            <slot name="title"></slot>
        </div>

        <div class="mt-5 md:mt-0" 
            :class="{'md:col-span-3': hasTitle, 'md:col-span-5': !hasTitle}">
            <div class="bg-white rounded-md shadow">
                <div class="py-7">
                    <slot></slot>
                </div>
            </div>
        </div>
    </div>
</template>

<script>
import {ref} from "vue";

export default {
    setup(props, {slots}) {
        const hasTitle = ref(false)

        // Check if the slot exists by name and has content.
        // It returns an empty array if it's empty.
        if (slots.title && slots.title().length) {
            hasTitle.value = true
        }

        return {
            hasTitle
        }
    }
}
</script>