首页 > 编程笔记 > JavaScript笔记 阅读:35

Vue slot插槽用法详解(附带实例)

在 Vue 中,slot(插槽)专门用于父组件向子组件传递标签结构,而不是单纯的数据。在使用时,一般会在子组件中通过 slot 来声明占位,在父组件中,通过子组件的标签体内向子组件传递标签结构。

插槽主要分为默认插槽、具名插槽、作用域插槽 3 种类型,同时,读者需要掌握插槽默认值。下面对相关内容进行讲解。

Vue默认插槽

前面介绍的父组件与子组件之间的通信方式 props 主要进行的是属性设置和传递,现在如果想要设置一些 HTML 标签结构和属性,那么利用 props 属性传递的模式会有些麻烦。

比如说,父组件想向子组件传递 title、content 等众多属性,其中每个属性都包含 HTML 标签元素(如 <Quote title="<h1>标题</h1>" content="<p>内容</p>".../>),试想子组件要如何接收并控制这些属性呢?

利用插槽的方式就可以轻松实现此类需求。在 components 目录下新建一个子组件文件 Quote.vue,在 App 父组件中引入并使用子组件 Quote,并在 Quote 组件标签的主体区域中直接设置 h1 和 p 标签内容,这样就实现了一个 slot 插槽内容的父组件向子组件传递的操作。

App.vue 文件代码如下:
<script setup>
import Quote from './components/Quote.vue';
</script>

<template>
  <quote>
    <h1>标题</h1>
    <p>内容</p>
  </quote>
</template>
现在只需在子组件文件 Quote.vue 中,利用 slot 插槽标签对父组件传递过来的插槽内容进行接收、渲染、显示即可。运行程序后,发现子组件中的 slot 组件实现的是插槽内容的占位和渲染、显示。

components/Quote.vue 文件代码如下:
<template>
  <h3>Quote 组件标题</h3>
  <slot></slot>
</template>
值得一提的是,slot 有一个默认值为 default 的 name 属性,它会对没有具体命名或指定为 default 的插槽内容进行占位和渲染,我们将它称为默认插槽。

默认插槽的完整写法如下:
<slot name="default"></slot>

默认插槽内容的完整写法如下:
<quote>
  <template v-slot:default>
    <h1>标题</h1>
    <p>内容</p>
  </template>
</quote>
默认插槽的页面效果和标签结构如下图所示:


图 1 默认插槽的页面效果和标签结构

Vue具名插槽

前面的代码中,App 父组件中的 Quote 子组件的插槽内容设置了 h1 与 p 两个标签。其实我们可以在子组件中为其设置具体的 name 名称,从而接收指定元素,还可以在组件中利用具名插槽控制插槽内容的显示。

将 components/Quote.vue 文件的代码修改成下方代码:
<template>
  <!-- content 内容放置到了 title 的前面 -->
  <slot name="content"></slot>
  <slot name="title"></slot>
</template>
那么在父组件中,我们就可以利用 template 块级代码和 v-slot 属性传递带名称的插槽内容。

修改后的 App.vue 文件代码如下:
<template>
  <quote>
    <template v-slot:title>
      <h1>标题</h1>
    </template>
    <template v-slot:content>
      <p>内容</p>
    </template>
  </quote>
</template>
在上方代码中,“v-slot: title”与“v-slot: content”已经明确对应的插槽名称是 title 与 content,与子组件中的 slot 的 name 属性是一一对应关系。现在刷新页面将会看到,content 内容在 title 标题的上方,因为子组件的具名插槽不仅控制了接收的元素,还明确了显示的位置,如下图所示。


图 2 content内容在title标题的上方

值得一提的是,template 标签在页面渲染时并不会渲染,它只是用于包裹渲染元素的内容而已。

Vue插槽默认值

在子组件中直接设置 slot,并在对应 slot 中设置 name 属性对应父组件中的插槽内容。在前面代码的基础上新增一个副标题内容(name 为 subTitle)的设置,如果父组件没有传递 subTitle,那么将直接渲染刚刚设置的插槽默认值,也就是这里所写的 p 标签“副标题”内容。

此时 components/Quote.vue 文件代码如下:
<template>
  <slot name="content"></slot>
  <slot name="title"></slot>
  <!-- 其实slot有一个默认值为default的name属性 -->
  <slot></slot>
  <!-- 如果父组件没有进行 v-slot:subTitle 副标题的内容传递,则会直接显示插槽默认值“副标题” -->
  <slot name="subTitle"><p>副标题默认内容</p></slot>
</template>
如果在父组件中进行了具名插槽 subTitle 的内容设置与传递,比如下方代码中的 h2 标签,那么最终页面显示的是父组件传递的 h2 标签内容。

此时 App.vue 文件代码如下:
<script setup>
import Quote from './components/Quote.vue';
</script>

<template>
  <quote>
    <template v-slot:title>
      <h1>标题</h1>
    </template>
    <template v-slot:content>
      <p>内容</p>
    </template>
    <!-- 除具名插槽以外,其他都是默认插槽,包括 hr 与 span -->
    <hr />
    <span>默认插槽</span>
    <!-- 如果 v-slot:subTitle 进行了传递,则显示传递的值 -->
    <template v-slot:subTitle>
      <h2>传递的副标题</h2>
    </template>
  </quote>
</template>
运行代码后,通过观察页面可以发现,此时页面上显示的副标题内容是“传递的副标题”,如下图所示。


图 3 页面上显示的副标题内容是“传递的副标题”

可以总结出,插槽默认值在没有传递时是默认值,在传递时则是传递的值。

Vue作用域插槽

当父组件中写的插槽内容需要使用子组件中的数据时,就会应用到作用域插槽。这种插槽类型通常是为了实现在父组件中进行更多页面化控制的需求。

现在有一个父组件 App,在这个组件中有一个列表数据 list,这个列表数据 list 可以进行属性传递,将数据传递至父组件 App 嵌套的子组件 List 中。

App.vue 文件代码如下:
<script setup>
import { ref } from "vue"
import List from "./components/List.vue"
const list = ref(["JavaScript", "Vue", "React", "Angular"])
</script>

<template>
  <List :list="list"></List>
</template>
子组件 List 将接收父组件 App 传递的数组数据,并利用 slot 插槽标签进行循环。在循环过程中还可以对 item、index 等元素对象和下标内容进行 slot 标签组件的绑定。

components/List.vue 文件代码如下:
<script setup>
defineProps(['list']);
</script>

<template>
  <ul>
    <li v-for="(item, index) in list" :key="item">
      <slot :item="item" :index="index"></slot>
    </li>
  </ul>
</template>
事实上,List 组件中的 slot 插槽标签获取了 item 与 index,并进行了插槽内容的回传。简单地说,就是将数据利用插槽形式回传到父组件中,而父组件则可以利用插槽调试方式直接调用子组件回传内容。

作用域插槽获取插槽内容的方法与具名插槽一样,依旧是利用 template 与 v-slot 结合获取,但 v-slot 获取的是子组件回传的一个对象,我们可以利用对象解构的方式直接获取 item 和 index 属性。既然获取了对象,那么在父组件中可以操控任意布局形式,比如通过取余实现类似“斑马线”的显示。

此时 App.vue 文件代码如下:
<script setup>
import { ref } from 'vue';
import List from './components/List.vue';
const list = ref(['JavaScript', 'Vue', 'React', 'Angular']);
</script>

<template>
  <List :list="list">
    <!-- 不解构写法 -->
    <template v-slot="scope">
      <!-- <b v-if="scope.index % 2">{{ scope.item }}</b> -->
      <!-- <i v-else>{{ scope.item }}</i> -->
    </template>
    <!-- 解构写法 -->
    <template v-slot="[item, index ]">
      <b v-if="index % 2">{{ item }}</b>
      <i v-else>{{ item }}</i>
    </template>
  </List>
</template>
运行代码后,页面效果如下图所示:


图 4 页面效果

相关文章