不只懂 Vue 语法:什麽是 slot?请示范 slot 的用法?

问题回答

slot(插槽)的概念是把外层的内容塞进子元件的指定位置里。跟插槽的字面意思一样,前提是:有插口才能插。子元件需要开一个插口(slot),才可以在外层元件把内容塞进子元件里。

slot(插槽) 可分为四种:

  • slot(插槽)
  • Named slot(具名插槽)
  • Scoped slot(作用域插槽)
  • Dynamic slot(动态插槽)

以下会再作详细解说。

slot(插槽)

先讲解第一个最简单使用 slot 的方法。

假设有一个叫 Card 的元件,我们想要从父层,把「夏日短裙」这文字塞进这个元件(即是子层),就可使用slot

结果:

以上就是 slot 概念:在外层把内容塞到子元件的指定位置里。这就是预设插槽,也是最简单的用法。

预设内容

避免没有传入内容,可以设定显示预值内容:

App.vue

<template>
  <Card></Card>
</template>

Card.vue

<template>
  <h1>
    <slot>这是预设文字</slot>
  </h1>
</template>

以上结果就会渲染「这是预设文字」。

Named slot (具名插槽)

借用这篇文章的解说例子,用下图就能理解为什麽要使用具名插槽:


图片来源:https://dev.to/samanthaming/how-i-m-using-vue-slots-on-my-site-nfn

假设有一个有很多工具的工具箱(父层内容),现在要把它们放到一个盒子里的不同间隔范围里(子元件)。这时候,盒子里要标示清楚这个范围是放什麽工具。正如在子元件里,要使用具名插槽来指定父层的内容要被指派塞进什麽地方。

例子:

结果:

注意:如果 slot 没有命名,在父元件传入内容时,需使用 v-slot:default 来标示。

另外,缩写可以用 # 代替 v-slot:

<Card>
    <template #title>
      <h1> 夏日短裙 </h1>
    </template>
    <template #price>
      <p> $200 </p>
    </template>
    <template #default>
      <p> Sizes: XS, S, M, L </p>
    </template>
</Card>

为什麽用 slot?与 props 有什麽分别?

在进入介绍最後一个 slot 用法,scoped slot 之前。先理解为什麽需要用 slot。props 与 slot 的分别是:

  • props 主要是用作传资料,不是传入 DOM 内容。例如,如果把<p> $200 </p> 这个 DOM 内容转入子元件里,这情况用 slot 就会方便很多。
  • 有时候资料是静态,不常变动,甚至需要大量重复。这情况无必要使用 props。
  • props 无法像 slot 分配资料呈现的位置。

利用 slot 打造自己的 UI 元件

参考 Mike 老师这个影片,他有提到使用 slot 打造自己的 UI 元件。过程就如自行制作一个 Vue 模版,利用 named slot 预先在分配资料的位置。当父层传入资料时,就自动把父层的资料,分配到子元件的不同位置里。

举例说,如果一个网页的每个 section heading 都有同一个样式,我可以把 heading 拆成一支独立档案。再同时利用 named slot 分配好不同资料的位置。这样的好处是:

  • Card 的 CSS 能拆出来写,不用写在父层里。
  • 比起使用 props,能自动分配资料到不同位置作呈现。

Heading.vue

<template>
  <div class="section">
    <h2>
      <slot name="title"></slot>
    </h2>
    <p>
      <slot name="slogan"></slot>
    </p>
  </div>
</template>

App.vue

<Heading v-for="section in sections" :key="section.title">
    <template #title>
      {{ section.title }}
    </template>
    <template #slogan>
      {{ section.slogan }}
    </template>
</Heading>
export default {
  components: {
    Heading,
  },
  data() {
    return {
      sections: [
        {
          title: "限时优惠",
          slogan: "抢购限时最便宜货品!",
        },
        {
          title: "最新商品",
          slogan: "为你带来最新货品!",
        },
        {
          title: "一般商品",
          slogan: "多种商品任你选择!",
        },
      ],
    };
  },
};

结果:

完整程序码

https://codesandbox.io/s/vue-slot-named-slot-zi-zhi-ui-yuan-jian-y7ej4?file=/src/components/Heading.vue

Scoped slot(作用域插槽)

Scoped slot 是指把子元件的资料取出来,给父层使用。这是因为父层无法取得子元件的资料。

举例说,每个商品都用一个 Card 元件来呈现。商品资料在父层,而 Card 的资料呈现和样式就在 Card 元件里面。像以下结构:

App.vue

<Card>
    <template #title>...</template>
    <template #price>...</template>
    <template #size>...</template>
</Card>
export default {
  components: {
    Card,
  },
  data() {
    return {
      items: [
        {
          title: "夏日短裙",
          price: 200,
          sizes: ["XS", "S", "M", "L"],
        },
        {
          title: "纯白 T-shirt",
          price: 100,
          sizes: ["XS", "S"],
        },
        {
          title: "针织背心",
          price: 300,
          sizes: ["XS", "S", "M"],
        },
      ],
    };
  },
};

components/Card.vue

  <div class="card">
    <h1 class="title">
      <slot name="title"></slot>
    </h1>
    <p>
        售价:
      <slot name="price"></slot>
    </p>
    <p>
      <slot name="size"></slot>
    </p>
    <div class="quantity">
      <label for="quantity">数量:</label>
      <select name="quantity" id="quantity" v-model="quantity">
        <option value="0">0</option>
        <option value="1">1</option>
        <option value="2">2</option>
        <option value="3">3</option>
      </select>
    </div>
    <p class="result">
      <slot name="result" :quantity="quantity" ></slot>
    </p>
  </div>
export default {
  data() {
    return {
      quantity: 0
    }
  }
};

结果每个 Card 都会长这样:

在这情况中,数量的 select 在子元件(Card.vue)里。可是,父层需要利用子元件 select 的结果,组成:已选购:{{ item.title }} x {{ quantity}} 这字串,再塞回子元件的 <slot name="result"></slot> 里面。

解决方法:

在子元件(Card.vue) 的 slot 里绑定 prop(我们称为 slot props),然後在父层的相应 template 里取出来用,并且组字串,塞回子元件里。

父层 App.vue

<template #result="slotProps">
  已选购:{{ item.title }} x{{ slotProps.quantity }}
</template>

子元件 Card.vue

<p class="result">
  <slot name="result" :quantity="quantity"></slot>
</p>

注意,props 名称不一定叫 slotProps,可自定义其他名称。再简洁一点,使用解构写法:

<template #result="{ quantity }">
  已选购:{{ item.title }} x{{ quantity }}
</template>

完整程序码

https://codesandbox.io/s/scoped-slot-yong-fa-dxqh4?file=/src/App.vue:320-416

Vuetify 经常使用 scoped slot

Vuetify是一个提供给Vue开发者的material UI 框架。

它有大量使用Scoped slot,让开发者客制化Vuetify里封装好的元件,以 table 这元件为例。这里有使用scoped slot,指定要更改Calories这个item里的内容:

Dynamic slot(动态插槽)

动态插槽很简单,就是在父层可以动态指定不同 slot,把内容塞到子元件里。

父层,使用中括号动态指定 slot:

<template>
  <div v-for="area in areas" :key="area">
    <input type="radio" :id="area" :value="area" v-model="chosenArea">
    <label :for="area"> {{ area }} </label>
  </div>
  <Color>
    <template v-slot:[chosenArea]> 
      <span>我现在在: {{ chosenArea }}</span>
    </template> 
  </Color> 
</template>
data() {
    return {
      chosenArea: 'nav',
      areas: ['nav', 'main', 'footer']
    }
}

子层,动态建立slot:

<p v-for="area in areas" :key="area" :class="area">
    <slot :name="area"></slot>
</p>
export default {
  data() {
    return {
      areas: ['nav', 'main', 'footer']
    }
  }
}
<style>
.nav {
  color: red;
}

.main {
  color: black;
}

.footer {
  color: blue;
}
</style>

结果

完整程序码

https://codesandbox.io/s/dynamic-slot-shi-fan-4yenc

总结

  • slot(插槽)的作用是把父层内容,塞到子元件里。有插口才能插,所以,子元件要先建立 slot,父层才可以把资料塞进去。
  • 常用情景是,利用 slot 打造自己的 UI 模版,把静态资料在父层塞进去这模版中。非常适合用於大量复制同样样式元件,以及传入静态资料的情况。
  • 使用 named slot(具名插槽)可以把资料分配到指定位置。
  • 使用 scoped slot(作用域插槽)可以把子元件里的资料,传出去给父层使用和处理,之後透过插槽塞回到子元件里。
  • 使用 dynamic slot(动态插槽)可以在父层动态指定插槽。

参考资料

Vue.js: slots vs. props
How I'm using Vue Slots on my site
The Difference Between Props, Slots and Scoped Slots in Vue.js
重新认识 Vue.js - 2-4 编译作用域与 Slot 插槽
Vue slots 使用超入门
复用元件的好帮手:Vue Slots(v-slot、Scoped Slots)


<<:  #11-下滑动态做起来!(JS: scrollTo & scrollBy)

>>:  [Android Studio 30天自我挑战] Progress Bar练习

[Day 26] Android Studio 七日陨石开发:嘘! 我正在监听这个元件

前言 昨天我们设计好UI介面後, 我们有一堆按钮和文字框的"元件", 要让这些元...

Day 29 - Vanilla JS Countdown Timer

前言 JS 30 是由加拿大的全端工程师 Wes Bos 免费提供的 JavaScript 简单应用...

DAY27 学习30天的c++

程序基本结构 程序的基本结构可概分为循序式结构、选择式结构,与重复式结构三种,几乎是在循序结构式的基...

JavaScript 闭包(Closure) 下集

看这个程序码,结果会是如何? function ArrFunction(){ var arr=[];...

Day10 跟着官方文件学习Laravel-Migration

Migration 是资料库的版本控制,让你和你的团队能够互相去共想资料库的结构,你是否曾经曾告诉你...