【vue】14.插槽:构建可复用组件的关键

今天看代码的时候碰到了插槽,有些看不懂,所以写下这篇文章,系统地梳理一下关于插槽的内容,也希望给大家带来一些帮助。

javascript 复制代码
// 我碰到的插槽长这样
<template  #default="scope">
   ... 
</template>

一.什么是插槽?

根据 Vue 官方的定义:"插槽用于向子组件传递内容。和 prop 用于传递数据不同,插槽用于传递更丰富的内容,包括 HTML 元素和其他 Vue 组件。"

简单来说,插槽就像是组件预留的"空位",允许向组件内部插入任何内容,无论是文本、HTML 元素还是其他组件。有了插槽组件更加灵活,可以根据不同的使用场景展示不同的内容。

听起来有点抽象,没关系,举个例子一下就懂了。

现在有一个界面,它是由许多个卡片组件构成的。我们称界面为父组件,卡片组件为子组件,如图:

先看看子组件和父组件的代码:

javascript 复制代码
// 卡片组件  子组件
<template>
  <div class="card">
    <slot name="header"></slot>
    <slot name="image"></slot>
  </div>
</template>
javascript 复制代码
// 在父组件中使用子组件
<CardComponent>
  <template #header>
    <h2>vue</h2>
  </template>
  <template #image>
    <img src="vue.jpg" alt="vue">
  </template>
  </template>
</CardComponent>

<CardComponent>
  <template #header>
    <h2>react</h2>
  </template>
  <template #image>
    <img src="react.jpg" alt="react">
  </template>
  </template>
</CardComponent>

// 后两个类似,省略

卡片组件包含标题和图片两部分,但其具体内容是由使用该组件的父组件决定的。那么,父组件如何知道内容应该传递到何处,以及如何进行传递呢?

传递到何处:

子组件通过定义带有特定名称的插槽来预留内容位置。例如,一个用于标题的插槽可以如下表示:

javascript 复制代码
<slot name="header"></slot>

name="header" 属性定义了一个名为 header 的插槽。

如何传递:

父组件需要将要传递的内容包裹在 <template> 标签内,并通过 # 符号后跟插槽名称来指定内容的插入位置。例如,要将标题传递到名为 header 的插槽,可以这样做:

javascript 复制代码
<template #header>
  <h2>这是卡片标题</h2>
</template>

这样,父组件就能精确地将内容传递到子组件中预定的插槽位置。


二.插槽的种类及其具体使用

理解了插槽是什么后,我们来看看不同类型的插槽。Vue 3 主要有三种插槽:默认插槽、具名插槽、作用域插槽。

1.默认插槽

默认插槽也叫匿名插槽,是最简单的插槽形式。不需要为 <slot> 标签提供 name 属性,并且在使用组件时,也不需要在 <template> 上指定插槽名称。如果一个组件只包含一个插槽,那我们就可以使用默认插槽。

毕竟只有一个坑,不管你给不给这个坑起名字,传进去的萝卜都只能待这个坑。所以没必要给坑起名字,也没必要给要传进去的萝卜指定坑位,一个萝卜一个坑,直接传就行了。

如上图所示,你可以为插槽指定默认内容,就像这样:

javascript 复制代码
<!-- child.vue -->
<template>
  <div>
    <slot>默认萝卜</slot>
  </div>
</template>

现在,当在父组件中使用 <child> 且没有提供任何插槽内容时,就会渲染"默认萝卜"。

但是,如果父组件提供了插槽内容,那么子组件的默认内容就会被替换掉。

javascript 复制代码
// 在父组件中使用子组件
<child>我是父组件传递的萝卜,我会取代之前的默认萝卜</child>

父萝卜驾到,通通闪开!


2.具名插槽

具名插槽,顾名思义,具有名字的插槽,也就是带"name"属性的插槽。

<slot> 元素可以有一个特殊的属性name,用来给各个插槽分配唯一的 ID,以确定每一处要渲染的内容。

如果我们需要给一个组件的多处地方填充内容,那么具名插槽就有用了。本文刚开始引入的卡片例子,就使用了具名插槽。

本质上是多个萝卜多个坑的问题。要怎么实现把萝卜放进对应的坑里?首先给坑位起名字,然后告诉萝卜,你要去哪个坑,就好了。

再举一个例子:

javascript 复制代码
// 在父组件中使用子组件
<child>
  <template #header>
    <h1>我是标题</h1>
  </template>

  <template #default>
    <p>我是第一段话</p>
    <p>我是第二段话</p>
  </template>

  <template #footer>
    <p>我是底部信息</p>
  </template>
</child>
javascript 复制代码
// 子组件
<div>

  <header>
    <slot name="header"></slot>
  </header>

  <main>
    <slot></slot>
  </main>

  <footer>
    <slot name="footer"></slot>
  </footer>

</div>

上面带name属性的就是具名插槽,如果不带name,那么那么它的name默认为default。当一个组件同时接收默认插槽和具名插槽时,所有位于顶级的非 <template> 节点都被隐式地视为默认插槽的内容。

所以上面的父组件也可以写成:

javascript 复制代码
// 在父组件中使用子组件
<child>
  <template #header>
    <h1>我是标题</h1>
  </template>

    <p>我是第一段话</p>
    <p>我是第二段话</p>

  <template #footer>
    <p>我是底部信息</p>
  </template>
</child>

如果你要把多个萝卜放入多个坑里,知道自己要去哪个坑的萝卜就会自己找到对应name的坑进去,其他的萝卜都进入默认的坑。


3.作用域插槽

(1)渲染作用域

在了解作用域插槽之前,有必要了解一下"渲染作用域"。

vue官网对"渲染作用域"的描述:

插槽内容可以访问到父组件的数据作用域,因为插槽内容本身是在父组件模板中定义的。举例来说:

javascript 复制代码
<span>{{ message }}</span>
<FancyButton>{{ message }}</FancyButton>

这里的两个 {``{ message }} 插值表达式渲染的内容都是一样的。

插槽内容无法访问子组件的数据。Vue 模板中的表达式只能访问其定义时所处的作用域,这和 JavaScript 的词法作用域规则是一致的。换言之:

父组件模板中的表达式只能访问父组件的作用域;子组件模板中的表达式只能访问子组件的作用域。

在 Vue 中,组件的作用域规则是这样的:

  • 父组件模板中的所有内容都是在父组件的作用域中编译的。在父组件模板中,可以访问父组件的数据和方法。
  • 子组件模板中的所有内容都是在子组件的作用域中编译的。在子组件模板中,可以访问子组件的数据和方法。

插槽内容也是如此:

  • 当在父组件中使用子组件并定义插槽内容时,这些插槽内容是在父组件的作用域中编译的。因此,可以在插槽内容中使用父组件的数据和方法,但不能直接使用子组件的数据和方法。
  • 子组件可以通过 props 接收来自父组件的数据,但是插槽内容本身不能直接访问子组件的数据。

那如果我们需要在插槽内容中使用子组件的数据呢?

那就要用到"作用域插槽"了。

(2)默认作用域插槽

在子组件slot标签绑定属性,然后在父组件中用v-slot进行接收,就可以把数据传递给父组件中。

在接收方式上,默认插槽和具名插槽有一些不同,先来看看默认插槽如何接收子组件传过来的props:

javascript 复制代码
//子组件
<template>
    <div>
        <slot personName="清清" age="3"></slot>
    </div>
</template>

//父组件

<template>
  <div>
    <Child v-slot="slotProps">
      我是 {{ slotProps.personName }} ,我今年 {{ slotProps.age }} 岁了。
    </Child>
  </div>
</template>
<script setup>
import Child from './Child.vue'
</script>

这里的slotProps是插槽 prop 的名字,可以自定义,方便后面使用。

也可以接收响应式数据:

(3)具名作用域插槽

具名作用域插槽允许给插槽起个名字,并且可以通过 v-slot:插槽名字="slotProps" 的方式来接收子组件传递的数据。如果用缩写的话,可以写成 #插槽名字="slotProps"

javascript 复制代码
<MyComponent>
  <template #header="headerProps">
    {{ headerProps }}
  </template>

  <template #default="defaultProps">
    {{ defaultProps }}
  </template>

  <template #footer="footerProps">
    {{ footerProps }}
  </template>
</MyComponent>

子组件传递数据给插槽::

javascript 复制代码
<slot name="header" message="hello"></slot>

插槽的 name 是 Vue 保留的属性,不会作为数据传递给插槽。所以,父组件拿到的headerProps,它只会包含 { message: 'hello' }

(4)注意事项

如果同时使用了具名插槽和默认插槽,那么需要为默认插槽明确地使用 <template> 标签。如果不这样做,可能会出现编译错误,因为这样容易混淆哪个数据属于哪个插槽。

举个例子,有一个组件 MyComponent,它有一个默认插槽和一个名为 footer 的具名插槽,我们不能直接在组件上使用 v-slot 指令,这样会导致数据的作用域不清楚。

所以,我们应该这样做:

javascript 复制代码
<MyComponent>
  <!-- 明确地使用默认插槽 -->
  <template #default="{ message }">
    <p>{{ message }}</p>
  </template>

  <!-- 具名插槽 -->
  <template #footer>
    <p>这里有一些联系信息</p>
  </template>
</MyComponent>

这样的话, message 只在默认插槽中可用,而在 footer 插槽中不可用。这样代码更清晰,也更不容易出错。

通过上述例子,我们可以看到插槽在 Vue 3 中的强大之处。它们不仅使组件更加灵活,还极大地提高了组件的可复用性。

相关推荐
狸克先生7 分钟前
如何用AI写小说(二):Gradio 超简单的网页前端交互
前端·人工智能·chatgpt·交互
sinat_3842410910 分钟前
在有网络连接的机器上打包 electron 及其依赖项,在没有网络连接的机器上安装这些离线包
javascript·arcgis·electron
baiduopenmap22 分钟前
百度世界2024精选公开课:基于地图智能体的导航出行AI应用创新实践
前端·人工智能·百度地图
loooseFish30 分钟前
小程序webview我爱死你了 小程序webview和H5通讯
前端
小牛itbull33 分钟前
ReactPress vs VuePress vs WordPress
开发语言·javascript·reactpress
请叫我欧皇i42 分钟前
html本地离线引入vant和vue2(详细步骤)
开发语言·前端·javascript
533_44 分钟前
[vue] 深拷贝 lodash cloneDeep
前端·javascript·vue.js
guokanglun1 小时前
空间数据存储格式GeoJSON
前端
GIS瞧葩菜1 小时前
局部修改3dtiles子模型的位置。
开发语言·javascript·ecmascript
zhang-zan1 小时前
nodejs操作selenium-webdriver
前端·javascript·selenium