当前位置: 首页 > article >正文

Vue演练场基础知识(七)插槽

为学习Vue基础知识,我动手操作通关了Vue演练场,该演练场教程的目标是快速体验使用 Vue 是什么感受,设置偏好时我选的是选项式 + 单文件组件。以下是我结合深入指南写的总结笔记,希望对Vue初学者有所帮助。

文章目录

  • 十五. 插槽
    • 插槽内容与出口
    • 渲染作用域
    • 默认内容
    • 具名插槽
    • 条件插槽
    • 动态插槽名
    • 作用域插槽
    • 具名作用域插槽
    • 高级列表组件示例
    • 无渲染组件

十五. 插槽

插槽内容与出口

父组件可以用props向子组件传递js表达式,emits向子组件传递事件,那能不能直接向子组件传递定义好的模板内容呢?
这就是插槽的作用。

<!-- Dialog.vue -->
<div class="dialog">
	<slot />
</div>
<Dialog>
	<div>对话框内容</div>
</Dialog>

最终渲染出的 DOM 是这样:

<div class="dialog">
	<div>对话框内容</div>
</div>

渲染作用域

由于插槽内容是在父组件里定义的,所以它能访问到父组件的数据作用域,不能访问子组件的。

<!-- Dialog.vue -->
<script>
export default {
	data() {
		return {msg: '来自子组件的内容'};
	}
}
</script>
<template>
	<div class="dialog">
		<slot />
	</div>
</template>
<script>
import Dialog from './Dialog.vue';
export default {
	components: {Dialog},
	data() {
		return {msg: '来自父组件的内容'};
	}
}
</script>
<template>
	<Dialog>
		<div>{{msg}}</div>
	</Dialog>
</template>

最终渲染出的 DOM 是这样:

<div class="dialog">
	<div>来自父组件的内容</div>
</div>

默认内容

<slot> 标签之间的内容可以作为默认内容,如果父组件使用了含有插槽的子组件但没有传入插槽内容,子组件中的插槽就使用默认内容。

<!-- Dialog.vue -->
<div class="dialog">
	<slot>默认内容</slot>
</div>
<Dialog>
	<div>对话框内容</div>
</Dialog>
<Dialog />

最终渲染出的 DOM 是这样:

<div class="dialog">
	<div>对话框内容</div>
</div>
<div class="dialog">
	默认内容
</div>

具名插槽

有时候我们希望子组件能接收多个插槽内容,比如希望一个对话框组件支持分别接收头部内容、主体内容、底部内容。对于这种场景, 元素可以有一个特殊的 attribute name,用来给各个插槽分配唯一的 ID。
这类带 name 的插槽被称为具名插槽 (named slots)。没有提供 name 的 <slot> 出口会被隐式地命名为“default”。

<!-- Dialog.vue -->
<div class="dialog">
	<!-- 对话框头部 -->
	<header class="card-header">
	  <slot name="header" />
	</header>
	<!-- 对话框主体 -->
	<main class="card-main">
	  <slot></slot>
	</main>
	<!-- 对话框尾部 -->
	<footer class="card-footer">
	  <slot name="footer">默认底部</slot>
	</footer>
</div>

与之<slot name="header">匹配的是包含模板内容的<template v-slot:header>,或简写为<template #header>

<Dialog>
	<!-- 对话框头部 -->
	<template #header>
		<div>我的标题</div>
	</template>
	<!-- 对话框主体 -->
	<template #default>
		<p>我的内容1</p>
		<p>我的内容2</p>
	</template>
	<!-- 对话框尾部 -->
	<template #footer>
		<button>取消</button>
		<button>确定</button>
	</template>
</Dialog>

当一个组件同时接收默认插槽和具名插槽时,所有位于顶级的非 <template> 节点都被隐式地视为默认插槽的内容。所以也可以省略掉<template #default>,把其中内容直接作子组件的直接子元素,但不能混用,即子组件的直接子元素中不能同时出现<template #default>和非 <template> 节点。

<Dialog>
	<!-- 对话框头部 -->
	<template #header>
		<div>我的标题</div>
	</template>
	<!-- 对话框主体 -->
	<p>我的内容1</p>
	<p>我的内容2</p>
	<!-- 对话框尾部 -->
	<template #footer>
		<button>取消</button>
		<button>确定</button>
	</template>
</Dialog>

最终渲染出的 HTML 如下:

<div class="dialog">
	<!-- 对话框头部 -->
	<header class="card-header">
		<div>我的标题</div>
	</header>
	<!-- 对话框主体 -->
	<main class="card-main">
		<p>我的内容1</p>
		<p>我的内容2</p>
	</main>
	<!-- 对话框尾部 -->
	<footer class="card-footer">
		<button>取消</button>
		<button>确定</button>
	</footer>
</div>

推荐在使用具名插槽的时候,为默认插槽使用显式的 <template> 标签,不容易混淆,更加可读。

条件插槽

在上面的例子中,我们为对话框组件的 header、footer 或 default 等插槽设置了margin等样式:

<header class="card-header">
  <slot name="header" />
</header>

但如果用户希望创建一个没有头部的对话框,于是不传header插槽内容(且没有默认插槽内容),那就会出现对话框顶部渲染出一个空的<header>

<header class="card-header"></header>

那么有没有办法在根据header插槽存在与否来决定要不要渲染<header>标签呢?
可以结合 $slots 属性与 v-if 来实现:

<header class="card-header" v-if="$slot.header">
  <slot name="header" />
</header>

其中的$slots表示父组件传入插槽的对象。
通常用于手写渲染函数,但也可用于检测是否存在插槽。
每一个插槽都在 this.$slots 上暴露为一个函数,返回一个 vnode 数组,同时 key 名对应着插槽名。默认插槽暴露为 this.$slots.default

// this.$slots等于
{
	default: () => <div>...</div>,
	header: () => <div>...</div>,
	footer: () => <div>...</div>,
}

动态插槽名

插槽名不仅可以设置为常量,还可以设置为变量,如以下设置插槽名为变量mySlotName

<!-- Dialog.vue -->
<div class="dialog">
  <slot :name="mySlotName">默认底部</slot>
</div>
<Dialog>
	<template #[mySlotName]>
		<button>取消</button>
		<button>确定</button>
	</template>
</Dialog>

作用域插槽

上文中提到,插槽是在父组件中被定义的,所以无法读取到子组件的状态。那假如插槽需要拿到子组件状态该怎么办呢?
可以像对组件传递 props 那样,向插槽出口<slot>上传入 attributes,实现把子组件的变量传递到插槽内容:

<!-- Dialog.vue -->
<div class="dialog">
	<slot msg="来自子组件的内容" /> <!-- 将子组件的状态传入slot -->
</div>
<Dialog v-slot="slotProps"> <!-- 设置一个slotProps变量接收来自父组件的插槽Props -->
	<div>{{slotProps.msg}}</div>
</Dialog>
// 或
<Dialog v-slot="{msg}">
	<div>{{msg}}</div>
</Dialog>

最终渲染出的 DOM 是这样:

<div class="dialog">
	<div>来自子组件的内容</div>
</div>

具名作用域插槽

作用域插槽也可以与具名插槽混用,如下面的v-slot:header="slotProps1"(也可写作#header="slotProps1):

<!-- Dialog.vue -->
<div class="dialog">
	<header>
		<slot name="header" msg="来自子组件的内容1" />
	</header>
	<main>
		<slot msg="来自子组件的内容2"/>
	</main>
</div>
 <Dialog>
 	<!-- header插槽 -->
	<!-- v-slot设置在<template>上,而不是<Dialog>上 -->
	<!-- v-slot:header="slotProps1" 也可以简写成 #header="slotProps1" -->
	<template #header="slotProps1"> 
	  <div>来自父组件的内容</div>
	  <div>{{slotProps1.msg}}</div>
	</template>
 	<!-- 默认插槽 -->
 	<!-- 支持在 v-slot 中使用解构 -->
 	<!-- 可写作 v-slot="{msg}" 或 v-slot:default="{msg}" 或 #default="{msg}" -->
	<template #default="{msg}">
		<div>{{msg}}</div>
	</template>
 </Dialog>

最终渲染出的 DOM 是这样:

<div class="dialog">
	<header>
		<div>来自父组件的内容</div>
		<div>来自子组件的内容1</div>
	</header>
	<main>
		<div>来自子组件的内容2</div>
	</main>
</div>

再次推荐为默认插槽使用显式的 <template> 标签,不容易出错。
不允许像下面这样
同时在子组件上和template上定义v-slot,否则会编译报错。

 <Dialog v-slot="slotProps2">
 	<!-- header插槽 -->
	<template v-slot:header="slotProps1"> 
	  <div>来自父组件的内容</div>
	  <div>{{slotProps1.msg}}</div>
	</template>
 	<!-- 默认插槽 -->
	<div>{{slotProps2.msg}}</div>
 </Dialog>

高级列表组件示例

通过对具名作用域插槽的运用,我们可以实现一个高级列表组件,封装了可重用的逻辑 (如数据获取、分页、无限滚动等) 和视图输出,并将部分视图输出(如列表项的内容和样式、每页条目数)通过作用域插槽交给了消费者组件来管理。

<!-- FuncyList.vue -->
<ul>
	<li v-for="item in list" key="item.id">
		<slot name="itemSlot" v-bind="item">
			{{item}}
		</slot>
	</li>
	每页{{pageNum}}个
</ul>
 <FuncyList :page-num="10" >
	<template #itemSlot="{id, title}"> 
	  <div>{{id}}-{{title}}</div>
	</template>
 </FuncyList>

无渲染组件

一些组件可能只包括了逻辑而不需要自己渲染内容,视图输出通过作用域插槽全权交给了消费者组件。我们将这种类型的组件称为无渲染组件。

<!-- NoRenderComponent.vue -->
<script>
export default {
	data() {
		return {pi: '3.1415926535'};
	},
	computed: {
		doublePi: () => this.pi *2;
	}
}
</script>
<template>
	<!-- 仅包含逻辑,不包含任何视图 -->
	<slot :text="doublePi" />
</temlate>
<NoRenderComponent >
	<template v-slot="{text}">
		<!-- 展示NoRenderComponent数据的视图写在插槽里 -->
		<div class="uiClass">{{text}}</div>
	</template>
</NoRenderComponent>

http://www.kler.cn/a/525179.html

相关文章:

  • JVM栈溢出线上环境排查
  • three.js用粒子使用canvas生成的中文字符位图材质
  • Mac m1,m2,m3芯片使用nvm安装node14报错
  • Fullcalendar @fullcalendar/react 样式错乱丢失问题和导致页面卡顿崩溃问题
  • 【教学类-89-02】20250128新年篇02——姓名藏头对联(星火讯飞+Python,五言对联,有横批)
  • 下载arm架构的deb包的方法
  • sentence_transformers安装
  • BGP分解实验·15——路由阻尼(抑制/衰减)实验
  • 关于Java的HttpURLConnection重定向问题 响应码303
  • 《DeepSeek R1:开启AI推理新时代》
  • C++实现2025刘谦魔术(勺子 筷子 杯子)
  • 第十六届蓝桥杯大赛软件赛(编程类)知识点大纲
  • 25年1月-A组(萌新)- 云朵工厂
  • 本地部署Deepseek R1
  • S价标准价与V价移动平均价的逻辑,以SAP MM采购订单收货、发票校验过程举例
  • 【Valgrind】安装报错: 报错有未满足的依赖关系: libc6,libc6-dbg
  • 【硬件测试】基于FPGA的QPSK+帧同步系统开发与硬件片内测试,包含高斯信道,误码统计,可设置SNR
  • 网络爬虫学习:应用selenium获取Edge浏览器版本号,自动下载对应版本msedgedriver,确保Edge浏览器顺利打开。
  • Vim安装与配置教程(解决软件包Vim没有安装可候选)
  • 【make】makefile变量全解
  • DeepSeek-R1 本地部署模型流程
  • 【愚公系列】《循序渐进Vue.js 3.x前端开发实践》032-组件的Teleport功能
  • 练习(复习)
  • Nginx 安装配置指南
  • ESP32-S3模组上跑通esp32-camera(37)
  • 什么是波士顿矩阵,怎么制作?AI工具一键生成战略分析图!