Vue自定义指定、插槽🛠️:
前言:当然既然学习框架的了,HTML+CSS+JS三件套必须的就不说了: JavaScript 快速入门
紧跟前文,目标学习Vue2.0——3.0: 懂个锤子Vue、WebPack5.0、WebPack高级进阶 涉及的技术栈…
学习前置链接: 懂个锤子Vue 项目工程化 懂个锤子Vue 项目工程化进阶⏫
自定义指令:
内置指令: 是Vue.JS
提供的一组内置的功能指令,它们以v-
前缀开始:v-text\v-bind\v-if
这些指令使得开发者能够以声明式的方式实现数据与视图的绑定,从而简化了DOM操作;
自定义指令:是Vue.js
框架中的一个核心特性,它允许开发者扩展Vue的模板语言:
实现对DOM元素的定制化操作,这种机制为开发者提供了直接操作控制DOM能力;
从而在不深入组件内部逻辑的情况下,增加或修改元素的行为;
自定义指令分为: 全局注册、局部注册;
全局自定义指令:
全局注册: 在Vue中让指令在整个应用程序中可用的方法,通过调用Vue.directive
方法完成;
Vue的静态方法 Vue.directive(id, definition)
: 它接受两个参数:
指令名称
:不需加v-前缀,Vue会自动添加,使用时需要加:v-指令名称
;
定义对象
:该对象内包含指令执行的生命周期钩子函数;
- bind: 指令第一次绑定到元素时触发
- inserted: 元素被插入到父节点时触发
- update: 组件内的数据变化导致VNode更新时触发
- componentUpdated: 组件及子组件更新完成后触发
- unbind: 指令从元素上解除绑定时触发
定义对象:内置生命钩子函数参数:
-
el: 指令所绑定的DOM元素,这使得你可以在指令的逻辑中直接操作DOM,比如:添加样式、修改属性或触发事件;
-
binding: 包含指令详细信息对象:
name
指令的名字、value
绑定到指令的值、expression
、arg
、modifiers
等属性; -
vnode 和 oldVnode: Vue编译后的虚拟节点,用于更复杂的操作;
vnode: Vue使用虚拟DOM(VNode)来表示真实的DOM元素,
oldVnode: 在更新过程中,oldVnode提供了更新前的虚拟节点状态
//main.JS 中定义全局指令:
Vue.directive('自定义指令名称', {
bind(el, binding, vnode) { /** 初始化操作 */ },
inserted(el, binding, vnode) { /** 元素插入DOM时的操作 */ },
update(el, binding, vnode, oldVnode) { /** 数据更新时的操作 */ },
componentUpdated(el, binding, vnode, oldVnode) { /** 组件及子组件更新后的操作 */ },
unbind(el, binding, vnode) { /** 解绑前的操作 */ }
});
注意: 全局注册需要定义在main.JS
中,或: 单独定义在JS文件中,在 main.js
引入并调用:
import Directives from './JS/directives' //自定义全局指令, JS文件目录;
import Vue from 'vue'
Vue.use(Directives);
全局注册自定义指令在Vue应用中可以简化和复用复杂的DOM操作逻辑,以下是一些应用场景: main.js
自动聚焦:创建一个v-focus
指令,使元素在插入页面时自动获取焦点;
//全局注册指令
Vue.directive('focus', {
//inserted 会在指令所在的元素,被插入到页面中时触发
inserted (el) {
// el 就是指令所绑定的元素
// console.log(el);
el.focus()
}
})
局部自定义指令:
局部注册: 是Vue.js
中一种:更加模块化和灵活的方式,为特定组件添加定制的DOM行为;
与全局注册不同: 局部指令仅在定义它的组件及其子组件中可用,这有助于减少全局命名空间的污染;
自定义指令语法,与全局类似: 在Vue2.x、3.x中,局部指令注册,通常在单文件组件script
部分,常规的Vue
组件中进行;
export default {
name: 'App',
//在Vue组件directives配置项中定义
directives: {
//自定义指令名:{ 指令的配置项 }
myfocus: {
inserted (el) {
el.focus(); //对el标签: 扩展额外功能;
}
}
}
}
自定义指令—传值:
需求: 实现一个 v-color
指令 - 传入不同的颜色,给标签设置文字颜色;
<template>
<div id="app">
<input type="text" v-model="color" >
<h1 v-color="color" >局部自定义指令</h1> <!-- 设置v-color值来自: 输入框,同步 -->
</div>
</template>
<script>
export default {
name: 'App',
//默认color 颜色: 红色;
data(){ return { color:"red" } },
//在Vue组件directives配置项中定义
directives: {
//指令名:指令的配置项
//对el标签: 扩展额外功能;
myfocus: { inserted (el) { el.focus(); } },
color: {
//inserted 提供的是元素被添加到页面中时的逻辑
inserted (el, binding) { el.style.color = binding.value },
//update 指令的值修改的时候触发,提供值变化后,dom更新的逻辑
update (el, binding) { el.style.color = binding.value }
}
}
}
</script>
<style></style>
插槽:
Vue.js
中的插槽Slot
是组件化开发中的一个核心特性:
它允许在 父组件 ——中向—— 子组件 传递和控制渲染的内容,从而实现更加灵活和复用的组件设计;
举例Demo: 我们经常遇到:引入组件模块样式、结构相同,但组件内容数据不同的情况,当然这也可以通过:父子传参解决
插槽Slot:
插槽Slot
: 是一种内容分发机制:
使得父组件可以将特定的HTML内容插入到子组件的特定位置,
这使得子组件的结构更加灵活,能够适应不同的内容需求,而不需要修改子组件的内部实现;
为什么需要插槽,不能通过其他方式来解决吗,父子组件通信也可以解决啊🙂:
-
内容的灵活性:
没有插槽的情况下,如果想要在子组件中显示不同的内容,通常需要将这些内容硬编码到子组件模板中;
或者,通过属性传递数据,但这限制了父组件对子组件内部结构的控制;
-
数据与结构的解耦:
直接通过属性传递数据并控制结构,可能会导致数据和展示逻辑紧密耦合,不便于维护和扩展
作用域插槽不仅传递数据,还允许父组件控制如何展示这些数据,
实现了数据和展示逻辑的分离,提高了代码的可维护性和可读性
-
组件的封装性与复用性:
如果每个个性化需求都要求修改子组件,这会破坏组件的封装性,使得维护变得困难;
通过插槽,子组件可以保持其核心功能和结构不变,同时允许外部内容插入,这样既保持了组件的纯净,又实现了复用;
总结: 插槽本质实现的功能和父子组件通信类似,但对于组件需要大量的数据修改,且伴随结构改变判断,单纯组件实现就会
变得麻烦,插槽定义了明确的填充
区域,使得组件的使用方式更加直观,提高了代码的可读性和可维护性;
定义插槽:
应用场景: 实现上述案例,组件警告弹框提示⚠️,插槽修改其内部数据;
插槽基本语法:
- 组件内需要定制的结构部分,改用
<slot></slot>
占位; - 使用组件时,
<组件>替换插槽内容</组件>
标签内部, 传入结构替换slot
./components/MyDialog.vue: 自定义组件插槽,警告弹框提示:内容由<slot>
占位,父组件定制传入;
<template>
<div class="dialog">
<div class="dialog-header">
<h3>警告:</h3>
<span class="close">✖️</span>
</div>
<div class="dialog-content">
<!-- 在需要定制的位置,使用slot占位 -->
<slot>插值:默认显示内容;</slot>
</div>
<div class="dialog-footer">
<button>取消</button>
<button>确认</button>
</div>
</div>
</template>
<script>
export default { data () { return { } } }
</script>
<!-- 省略样式代码 -->
<style scoped></style>
App.vue: 主组件调用——>插槽组件并传值,运行查看效果;
<template>
<div id="app">
<!-- 外部使用组件时,不传东西,则slot会显示后备内容 -->
<MyDialog></MyDialog>
<!-- 外部使用组件时,传东西了,则slot整体会被换掉 -->
<MyDialog>组件调用完成,传递数据替换插槽值;</MyDialog>
</div>
</template>
<script>
import MyDialog from './components/MyDialog.vue'
export default {
name: 'App',
data(){ return { } },
components:{ MyDialog }
}
</script>
<style></style>
具名插槽:
具名插槽(Named Slots
: 是Vue.js
中用于组件间内容分发的一种高级特性,它允许开发者在子组件中定义多个插槽,
父组件可以指定内容插入到子组件的特定插槽中,这种机制提高了组件复用性灵活性,特别是在构建复杂UI结构时
通常情况下,默认插槽:<组件> 替换插槽内容 </组件>
仅支持一个定义插槽,传值;
当子组件需要多个插槽
来接收不同部分的内容时,可以使用具名插槽:
具名插槽基本语法:
-
在子组件的模板中,通过给
<slot name="具名" >
元素添加name属性来定义具名插槽; -
父组件在使用子组件时,通过
v-slot
指令指定内容应该插入到子组件的哪个具名插槽中,Vue 3
中,可以直接在v-slot
后跟插槽名称,或者使用冒号前缀来指定;
./components/MyDialog.vue:
<template>
<div class="dialog">
<!-- 一旦插槽起了名字,就是具名插槽,只支持定向分发 -->
<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>
</div>
</template>
App.vue: 主组件调用——>插槽组件并传值,运行查看效果;
<template>
<div id="app">
<!-- 调用具名插槽需要通过template标签包裹需要分发的结构,包成一个整体 -->
<MyDialog>
<!-- 通过`v-slot`指令指定内容应该插入到子组件的哪个具名插槽中 -->
<template v-slot:head>我是大标题</template>
<template v-slot:content>我是内容</template>
<template v-slot:footer> <button>取消</button> <button>确认</button></template>
</MyDialog>
</div>
</template>
<script>
import MyDialog from './components/MyDialog.vue'
export default {
name: 'App',
data(){ return { } },
components:{ MyDialog }
}
</script>
<style></style>
作用域插槽:
作用域插槽Scoped Slots
是Vue.js
提供的一种高级插槽机制:
普通插槽: 某种意义上类似于,父组件——传递特定数据——渲染子组件,算是一种:父——子通信
作用域插槽: 它允许子组件向父组件传递数据,使得父组件在使用子组件的插槽时能够访问到子组件的内部数据;
这一特性在Vue 2.6中引入,并在Vue 3中通过更简洁的v-slot语法得到进一步的优化和推广;
作用域插槽的核心在于,它创建了一个局部作用域: 这个作用域内的数据由子组件提供;
父组件可以通过插槽来访问这些数据,这使得父组件可以根据子组件的状态\数据:
动态地渲染内容,而无需直接访问子组件的内部状态;
作用域插槽语法:
- 子组件:给
slot
标签以添加属性的方式传值:所有添加的属性,都会被收集到一个对象中传递; - 父组件:
template
中通过#插槽名= "变量名"
接收确认匹配的插槽,并将数据赋值变量名方便使用,默认插槽名为#defaul
Demo案例:
封装表格组件: 数据由父组件提供,传递子组件渲染表格,但:数据修改\删除\查询操作还在父组件;
子组件仅是单纯的渲染数据,并支持根据需求自定义:修改\删除\查询操作按钮权限;
而:父组件操作表格信息,就要获取对应信息的ID: 子组件—通过插槽形式—传递父组件
./components/MyDialog.vue:
<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>
<!-- 给slot标签添加属性的方式传值 -->
<!-- 所有添加的属性,都会被收集到一个JSON对象中发送至父组件 -->
<slot :row="item" msg="测试文本" ></slot>
</td>
</tr>
</tbody>
</table>
</template>
<script>
export default {
props: { data: Array } //接受父组件传递数据;
}
</script>
App.vue: 主组件调用——>插槽组件并传值,运行查看效果;
<template>
<div id="app">
<!-- 引入表格组件: 并自定义其支持删除操作 -->
<MyTable :data="list" > <!-- :data 父—传递—子数据 -->
<template #default="obj"> <!-- 父组件自定义组件按钮,并获取子组件传递数据信息; -->
<button @click="del(obj.row.id)">删除</button> <!-- 在template中,通过 #插槽名="自定义变量名" 接收,默认插槽名为 #default -->
</template>
</MyTable>
<!-- 引入表格组件: 并自定义其支持查询操作 -->
<MyTable :data="list" >
<template #default="obj">
<button @click="del(obj.row.id)">删除</button>
<button @click="show(obj.row)">查看</button>
<button @click="showdata(obj)">数据</button>
</template>
</MyTable>
</div>
</template>
<script>
import MyTable from './components/MyTable.vue'
export default {
name: 'App',
data(){
return { //定义表格组件渲染数据:
list: [
{ id: 1, name: '张小花', age: 18 },
{ id: 2, name: '孙大明', age: 19 },
{ id: 3, name: '刘德忠', age: 17 },
]
}
},
methods: { //父组件对应操作函数;
del (id) { this.list = this.list.filter(item => item.id !== id) },
show (row) { alert(`姓名:${row.name}; 年纪:${row.age}`) },
showdata(row) { console.log(row) }, //将子组件solt中定义的值封装成一个JSON对象全传递过来;
},
components:{ MyTable }
}
</script>
代码管理:
本代码已经使用Git进行管理: 公众号回复:Vue项目工程化