插槽的作用:让组件内部的一些 结构 支持自定义。
例如:我们需要再页面中显示一个对话框,将其封装成一个组件。
当我们希望组件内容部分,不希望写死,就得用插槽来解决
插槽的分类:
基本使用
MyDialog组件
1.组件内需要定制的结构部分,使用<slot></slot>占位
<div class="dialog-content">
<!-- 1. 在需要定制的位置,使用slot占位 -->
<slot></slot>
</div>
App.vue组件
2.使用组件时,<MyDialog></MyDialog>标签内部,传入结构替换slot。
<div class="app">
<!-- 2. 在使用组件时,组件标签内填入内容 -->
<MyDialog>
<div>你确认要退出么</div>
</MyDialog>
<MyDialog>
<p>你确认要删除吗</p>
<p>你确认要删除吗</p>
<p>你确认要删除吗</p>
</MyDialog>
</div>
通过插槽完成了内容的定制,传什么显示什么,但是如果我们在组件使用的时候,不传,则是空白
那么能否给插槽设置 默认显示内容呢?
MyDialog组件
1.
<div class="dialog-content">
<!-- 往slot标签内部,编写内容(会被作为后备默认内容) -->
<slot>我是默认的插槽内容</slot>
</div>
App.vue组件
<div class="app">
//一个不传值,一个传值
<MyDialog></MyDialog>
<MyDialog>
我传递了内容哦!
</MyDialog>
</div>
说白了就是当不给组件传值的时候,就显示slot标签内部的内容。
当一个组件有多个结构,需要进行定制内容
此时的默认插槽是无法解决该问题
默认插槽:只能有一个定制位置
所以,得使用具名插槽来解决
MyDialog组件
1.
<div class="dialog-header">
<!-- 一但插槽起了名字,就是具名插槽,(只支持定向分发) -->
<slot name="head"></slot>
</div>
<!-- 内容 -->
<div class="dialog-content">
<slot name="content"></slot>
</div>
<!-- 底部 -->
<div class="dialog-footer">
<slot name="footer"></slot>
</div>
App.vue组件
2.切记用template包裹起来,根据插槽名分发内容
<MyDialog>
<!-- 需要通过template包裹,template配合 v-slot:名字 来分发对应标签 -->
<template v-slot:head>
<div>我是大标题</div>
</template>
<template v-slot:content>
<div>我是内容</div>
</template>
<!-- 简写#插槽名 -->
<template #footer>
<button>确认</button>
<button>取消</button>
</template>
</MyDialog>
作用于插槽:定义slot插槽的同时,是可以传值的。给 插槽 上可以 绑定数据,将来 使用组件时可以使用。
使用场景:封装表格组件
所有添加的属性,都会被收集到一个对象中
{ id: 3 , msg: '测试'}
在 template中,通过 #插槽名="obj"
接收,默认插槽名为: default → #default="obj"
这里使用一个简单的表格删除和查询来练习插槽
首先是我们呢的子组件代码
<template>
<table class="my-table">
<thead>
<tr>
<th>序号</th>
<th>姓名</th>
<th>年纪</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr v-for="(item,index) in data" :key="item.id">
<td>{{ index + 1 }}</td>
<td>{{ item.name }}</td>
<td>{{ item.age }}</td>
<td>
<!-- 定义插槽的同时,可以传值哦(使用插槽作用域) -->
<!-- 1. 给slot标签,添加属性的方式传值 -->
<slot :row="item" msg="测试">默认插槽</slot>
<!-- 2. 将所有的属性添加到一个对象中 -->
<!--
{
row : {id: 2 ,name: "孙大明", age: 19},
msg: '测试文本'
}
-->
</td>
</tr>
</tbody>
</table>
</template>
<script>
export default {
props: {
data: Array
}
}
</script>
App.vue父组件代码
<template>
<div>
<MyTable :data="list">
<!-- 3. 通过template #插槽名="变量名" 接收 -->
<template #default = "obj">
<button @click="del(obj.row.id)">删除</button>
</template>
</MyTable>
<MyTable :data="list2">
<!-- 这里是一样的:需要使用template包裹 -->
<template #default="{row}">
<button @click="look(row)">查看</button>
</template>
</MyTable>
</div>
</template>
<script>
import MyTable from './components/MyTable.vue'
export default {
data () {
return {
list: [
{ id: 1, name: '张小花', age: 18 },
{ id: 2, name: '孙大明', age: 19 },
{ id: 3, name: '刘德忠', age: 17 },
],
list2: [
{ id: 1, name: '赵小云', age: 18 },
{ id: 2, name: '刘蓓蓓', age: 19 },
{ id: 3, name: '姜肖泰', age: 17 },
]
}
},
methods: {
del (id) {
// 删除功能:使用filter过滤
this.list = this.list.filter( item => item.id !== id)
},
look (row) {
alert(`姓名:${row.name}; 年纪:${row.age}`)
}
},
components: {
MyTable
}
}
</script>
什么是自定义指令?
1、例如:完成自动聚焦的功能:
// 全局注册指令
Vue.directive('focus',{
// inserted会在 指令所在的元素,被插入到页面中的时候触发
inserted (el) {
// el 就是我们指令所绑定的元素
el.focus()
}
})
// 2.局部注册指令
directives: {
// 指令名:指令配置项
focus: {
// el 是我么指令所绑定的元素
inserted (el) {
el.focus()
}
}
}
根据使用场景:选择不同的注册方式。
2、例如:实现一个color指令 - 传入不同的颜色,给标签设置文字颜色
语法:
我们再绑定指令的时候,可以通过 等号 给我们指令绑定具体的参数。
v-指令名=“指令值”,绑定指令的值
通过 binding.value可以可以拿到指令值,指令值修改会 触发 update 函数
binding.value可以拿到color的值,当我们指令被修改后,会执行update函数
<div class="app">
<h1 v-color="color1">指令的值1</h1>
<h1 v-color="color2">指令的值2</h1>
</div>
<script>
export default {
data () {
return {
color1: 'red',
color2: 'green'
}
},
// 注册自定义指令
directives: {
color: {
// 1、inserted 元素被添加到页面时的逻辑
inserted (el,binding) {
// binding.value 就是指令的值
el.style.color = binding.value
},
// 2、update 指令的值修改的时候触发,提供dom更新后的逻辑
update (el, binding) {
el.style.color = binding.value
}
}
}
}
</script>
分析 v-loading指令封装
实现:
准备一个 loading 类,通过伪元素定位,设置宽高,实现蒙层
开启关闭蒙层状态(添加移除蒙层),本质只需要添加移除类即可
结合自定义指令的语法进行封装
<div class="box" v-loading="isLoading">
<script>
// 安装axios => yarn add axios
import axios from 'axios'
// 接口地址:http://hmajax.itheima.net/api/news
// 请求方式:get
export default {
data () {
return {
list: [],
isLoading: true
}
},
// 定义局部指令
directives: {
loading: {
inserted (el,binding) {
// 指令触发后执行当前钩子 inserted
binding.value ? el.classList.add('loading') : el.classList.remove('loading')
},
update (el,binding) {
// 指令值被修改执行当前钩子 update
binding.value ? el.classList.add('loading') : el.classList.remove('loading')
}
}
},
async created () {
// 1. 发送请求获取数据
const res = await axios.get('http://hmajax.itheima.net/api/news')
setTimeout(() => {
// 2. 更新到 list 中,用于页面渲染 v-for
this.list = res.data.data
// 数据响应后,改变蒙层状态
this.isLoading = false
}, 2000)
}
}
</script>
<style>
.loading:before {
content: '';
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
background: #fff url('./loading.gif') no-repeat center;
}
</style>
[外链图片转存中…(img-AOMZCmKC-1693238901398)]
MyTag 组件的封装
创建组件 → 初始化
实现功能
(1)双击显示,并开启自动聚焦
1. v-if v-else @dblclick
2. 通过注册全局的自定义指令获取焦点 focus
(2)失去焦点的时候,隐藏输入框
1. 然后使用@blur失去焦点隐藏
(3)回显标签信息
回显的标签信息是父组件传递过来的
(4)内容修改了,回车 → 修改标签信息
1. @keyup.enter ,触发了回车事件 执行 $emit(‘input’,e.target.value)
2. 然后父组件中的 v-model 底层的 @input自动帮我们执行数据渲染回显
My-Table 表格组件的封装
使用: props
结构不能写死 → 多处结构实现自定义 → 具名插槽
(1) 表头需要支持自定义
(2) 主体支持自定义
1.导入
import MyTag from './components/MyTag.vue'
import MyTable from './components./Mytable.vue'
2. 注册
components: {
MyTag,
MyTable
}
3. 使用
<MyTag></MyTag>
<MyTable></MyTable>
1.双击显示
1.双击显示
<div v-else @dblclick="handleedit" class="text">{{ value }}</div>
//提供以下方法
handleedit() {
// 双击切换到显示状态
this.isShow = true;
},
2 自动聚焦
2.自动聚焦
// 封装全局自定义指令(实现获取焦点),当然可以使用$nextTick解决同步Vue的问题,达到异步更新效果
Vue.directive('focus', {
// el 是我们指令所在的dom元素
inserted (el) {
el.focus()
}
})
失去焦点,隐藏输入框
使用blur事件失去焦点,然后将isShow改为false
@blur="isShow = false"
在我们双击了触发了编辑功能后,显示了输入框,然后再自动获得focus。
使用v-model将父组件的值传递过来
然后子组件使用 props 接受 进行操作
给组件绑定 v-model 指令
<MyTag v-model="item.tag"></MyTag>
props接收数据,并回显渲染
1. props接收数据
// 接收父组件传过来的数据 :value
props: {
value: String,
},
2. 回显渲染
//将数据动态的赋值给我们的需要显示的标签
:value="value"
@keyup.enter="handleEnter"
1. 通过$emit('事件名',传递的值) 通知父组件
methods: {
handleEnter (e) {
//子传父,将获取到的输入框给父组件,通知他修改渲染标签
if (e.target.value.trim() === '') {
alert('修改的内容不能为空')
return
//通过 input事件 同志父组件监听,并修改输入框内容
this.$emit('input',e.target.value)
}
}
}
2. 回车后,输入框隐藏
this.isShow = false
到此,我们的编辑功能组件实现完毕
数据在App.vue中,使用父传子实现
1. 动态的属性传递数据
<MyTable :data="item.goods"></MyTable>
2. 接收数据
props: {
data: {
type: Array,
required: true 控制判断
}
}
1.表头自定义:切记使用具名插槽,因为我们需要在定制的时候,用到相互之间的数据。
<!-- 使用插槽动态的定制了表头结构 -->
<template #head>
<th>编号</th>
<th>名称</th>
<th>图片</th>
<th width="100px">标签</th>
</template>
2. 主体定制:同样的使用具名插槽,需要制定并且传递该组件的数据
<!-- 使用插槽作用域传递值,定制主体的内容 -->
<template #body="{ item, index }">
<td>{{ index + 1 }}</td>
<td>{{ item.name }}</td>
<td>
<img :src="item.picture" />
</td>
<td>
<MyTag v-model="item.tag"></MyTag>
</td>
</template>
编辑渲染功能和自定义结构功能就实现完毕了
<template>
<div class="table-case">
<MyTable :data="goods">
<!-- 使用插槽动态的定制了表头结构 -->
<template #head>
<th>编号</th>
<th>名称</th>
<th>图片</th>
<th width="100px">标签</th>
</template>
<!-- 使用插槽作用域传递值,定制主体的内容 -->
<template #body="{ item, index }">
<td>{{ index + 1 }}</td>
<td>{{ item.name }}</td>
<td>
<img :src="item.picture" />
</td>
<td>
<MyTag v-model="item.tag"></MyTag>
</td>
</template>
</MyTable>
</div>
</template>
--------------------------------------------------------
<script>
export default{
import MyTag from './components/MyTag.vue'
import MyTable from "./components/MyTable.vue";
export default {
// 2.注册组件
components: {
MyTag,
MyTable,
},
name: "TableCase",
data() {
return {
tempText: "水杯",
tempText2: "钢笔",
goods: [
{
id: 101,
picture:
"https://yanxuan-item.nosdn.127.net/f8c37ffa41ab1eb84bff499e1f6acfc7.jpg",
name: "梨皮朱泥三绝清代小品壶经典款紫砂壶",
tag: "茶具",
},
{
id: 102,
picture:
"https://yanxuan-item.nosdn.127.net/221317c85274a188174352474b859d7b.jpg",
name: "全防水HABU旋钮牛皮户外徒步鞋山宁泰抗菌",
tag: "男鞋",
},
{
id: 103,
picture:
"https://yanxuan-item.nosdn.127.net/cd4b840751ef4f7505c85004f0bebcb5.png",
name: "毛茸茸小熊出没,儿童羊羔绒背心73-90cm",
tag: "儿童服饰",
},
{
id: 104,
picture:
"https://yanxuan-item.nosdn.127.net/56eb25a38d7a630e76a608a9360eec6b.jpg",
name: "基础百搭,儿童套头针织毛衣1-9岁",
tag: "儿童服饰",
},
],
};
},
};
</script>
<template>
<div class="my-tag">
<input
v-if="isShow"
v-focus
@blur="isShow = false"
:value="value"
@keyup.enter="handleEnter"
class="input"
type="text"
placeholder="输入标签"
/>
<div v-else @dblclick="handleedit" class="text">{{ value }}</div>
</div>
</template>
---------------------------------------------------
<script>
export default {
// 接收父组件传过来的数据 :value
props: {
value: String,
},
data() {
return {
isShow: false,
};
},
methods: {
handleedit() {
// 双击切换到显示状态
this.isShow = true
},
handleEnter(e) {
// 子传父,将{输入框的内容}传递给我们的父组件,通知他叫他渲染
// 由于父组件是 v-model 所以触发事件为 @input
if (e.target.value.trim() === "") return alert("修改内容不能为空")
this.$emit("input", e.target.value)
// 回车后输入框隐藏
this.isShow = false
},
},
};
</script>
<template>
<table class="my-table">
<thead>
<tr>
<slot name="head"></slot>
</tr>
</thead>
<tbody>
<tr v-for="(item,index) in data" :key="item.id">
<slot name="body" :item = item :index = index></slot>
</tr>
</tbody>
</table>
</template>
----------------------------------------------------
<script>
export default {
props: {
data: {
type: Array,
required: true
}
}
}
</script>