vue核心技术与实战
自定义指令
自定义指令:自己定义的指令,可以封装一些dom操作,扩展额外功能
基本语法
全局注册:
// 1. 全局注册指令
Vue.directive('focus', {
// inserted 会在 指令所在的元素,被插入到页面中时触发
inserted (el) {
// el 就是指令所绑定的元素
// console.log(el);
el.focus()
}
})
局部注册:
directives:{
loading:{
inserted(el,banding){
banding.value ? el.classList.add('loading') : el.classList.remove('loading')
},
update(el,banding){
banding.value ? el.classList.add('loading') : el.classList.remove('loading')
}
}
}
需求:当页面加载时,让元素获得焦点
指令的值
需求:实现一个color指令-传入不同的颜色,给标签设置文字颜色
语法:
- 在绑定指令时,可以通过=的形式为指令绑定具体的参数值
2. 通过binding.value可以拿到指令值,指令值修改会触发update函数
v-loading指令封装
场景:实际开发中没发送请求需要时间,在请求的数据没有回来时,页面会处于空白状态,用户体验不好
需求:封装一个v-loading指令,实现加载中的效果
分析:
- 本质loading效果就是一个蒙层,盖在了盒子上
- 数据请求中,开启loading状态,添加蒙层
- 数据请求完毕,关闭loading状态,移除蒙层
实现:
- 准备一个loading类,通过伪元素定位,设置宽高,实现蒙层
.loading:before{
content: '';
position:absolute;
left: 0;
right: 0;
width: 100%;
height: 100%;
background: #fff url(./loading.gif) no-repeat center;
}
- 开启关闭loading状态(添加移除蒙层),本质只需要添加移除类即可
<template>
<div>
<div class="box" v-loading="isLoading">
<ul>
<!-- v-for v-if v-else 用来循环数据,选择展示标签 -->
<li v-for="item in list" :key="item.id" class="news">
<div class="left">
<div class="title">{{ item.title }}</div>
<div class="info">
<span>{{ item.source }}</span>
<span>{{ item.time }}</span>
</div>
</div>
<div class="right">
<img :src="item.img" alt="">
</div>
</li>
</ul>
</div>
<div class="box2" v-loading="isLoading2"></div>
</div>
</template>
定义v-if v-else具体展示哪个标签(控制loading类到底是添加还是移除)
data () {
return {
isLoading:true,
isLoading2:true,
list: []
}
},
- 结合具体使用哪个(私有还是全局)自定义指令的语法进行封装复用
inserted钩子中,binding.value判断指令的值,设置默认状态
update钩子中,binding.value判断指令的值,更新状态
//私有自定义指令
directives:{
loading:{
inserted(el,banding){
banding.value ? el.classList.add('loading') : el.classList.remove('loading')
},
update(el,banding){
banding.value ? el.classList.add('loading') : el.classList.remove('loading')
}
}
}
//全局自定义指令
Vue.directive('loading',{
instered(el,binding){
binding.value?el.classList.add('.loading'):el.classList.remove('loading')
},
update(el,bingding){
binding.value?el.classList.add('.loading'):el.classList.remove('loading')
}
插槽
默认插槽
作用:让组件内部的一些结构支持自定义
需求: 将需要多次显示的对话框,封装成一个组件
问题:组件的内容部分,不希望写死,希望能使用的时候自定义,怎么办?
插槽基本语法:
- 组件内需要定制结构部分改用
<slot></slot>占位 - 使用组件时,
<Mysialog></Mydialog>标签内部,传入结构替换slot
后备内容(默认值)
通过插槽完成了内容的定制,传什么显示什么,但是如果不传,则是空白,在<slot>标签内,放置内容,作为默认显示内容
具名插槽
需求:一个组件内有多处结构从外部标签传入,所以需要区分这几个标签分别放在哪个位置,需要定制名字进行区分
具名插槽语法:
-
多个slot使用name属性区分名字
-
template配合v-slot:名字来分发对应标签
简化语法:
作用域插槽
定义slot插槽的同时是可以传值的,给插槽上可以绑定数据,将来使用组件时可以用 基本使用步骤:
- 给slot标签,以添加属性的方式传值
<slot :id='item.id' msg='测试文本'></slot>
- 所有添加的属性,都会被收集到一个对象中
{id:3,msg:'测试文本'}
- 在template中,通过
#插槽名="obj"接收,默认插槽名伪default
<MyRTable :list='list'>
<template #default="obj">
<button @click="del(obj.id)">删除</button>
</template>
</Mytable>
场景:封装表格组件
- 父传子,动态渲染表格内容
- 利用默认插槽,定制操作列
- 删除或查看都需要用到当前项的id,属于组件内部的数据,通过作用域插槽传值绑定,进而使用
父组件App
//父组件
<template>
<div>
<MyTable :data="list"><!-- 父传子,绑定list数据动态渲染表格内容,在子组件中props中接收 -->
<template #default="obj"> <!-- 在子组件中slot传过来的值,使用 #插槽名字接收 -->
<button @click="del(obj.row.id)">删除</button><!--绑定点击事件,里面的值是来自从插槽接收到的值-->
</template>
</MyTable>
<MyTable :data="list2">
<template #default="obj">
<button @click="show(obj.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){
this.list =this.list.filter(item=> item.id!==id)
},
show(row){
// console.log(obj)
alert(`姓名:${row.name} 年龄:${row.age}`)
}
},
components: {
MyTable
}
}
</script>
子组件Mytable
//子组件
<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: ,name: ,age : },
msg:测试项目
}
-->
</td>
</tr>
</tbody>
</table>
</template>
<script>
export default {
props:{
data:Array
}
}
</script>
综合案例:商品列表
Mytag组件封装
mt-Tag组件的封装(表格结构的渲染)
- 双击显示输入框,输入自动框自动获取焦点
v-if v-else @dblclick 操作isapper 控制输入框的显示
自动聚焦: ①:nextTick(()=>{$refs})获取到dom,进行focus获取焦点②:封装v-focus全局指令 标签内使用v-focus命令即可
-
失去焦点,隐藏输入框
@blur操作isappear即可
-
回显标签信息
回显的标签信息是父组件传递过来的 v-model实现功能(简化代码)v-model=>:value和@input 组件内部通过props接收,value设置给输入框
- 内容修改,回车-->修改标签信息
@keyup.enter,触发事件$emit('input',e.target.value)
Mytable组件封装
my-table 表格组件的封装(表格中的具体数据渲染)
- 数据不能写死,动态传递表格渲染的数据 props
- 结构不能写死 - 多处结构自定义 【具名插槽】
(1) 表头支持自定义(2) 主体支持自定义
案例源代码
My-Tag组件代码:
//My-Tag组件
<template>
<div class="my-tag">
<input
v-if="isappear"
ref="inp"
v-focus
class="input"
type="text"
placeholder="输入标签"
@blur="isappear=false"
@keyup.enter="handelenter"
/>
<div class="text" v-else @dblclick="handleclick">{{value}}</div>
<!-- 1. v-if和v-else控制二者到底谁显示或隐藏
2. ref获取当前元素
3. v-focus是注册的自定义全局指令,表单一显示就自动获取焦点
4. @blur是失去焦点监听事件
5. @keyup是键盘按下回车键的监听事件
6. @dblclick是监听鼠标双击事件--鼠标双击之后展示表单且把value值回显在表单上-->
</div>
</template>
<script>
export default {
props:{
value:String
}
,
data(){
return{
isappear:false //布尔值控制输入框与标签到底谁显示
}
},
methods:{
handleclick(){
//双击后切换到显示状态(vue是异步dom更新)
this.isappear=true
//等dom更新完了再获取焦点
/* this.$nextTick(()=>{
//立刻获取焦点
this.$refs.inp.focus()
}) */
},
handelenter(e){
this.$emit('input',e.target.value)
this.isappear=false
}
}
}
</script>
<style lang="less">
.my-tag {
cursor: pointer;
.input {
appearance: none;
outline: none;
border: 1px solid #ccc;
width: 100px;
height: 40px;
box-sizing: border-box;
padding: 10px;
color: #666;
&::placeholder {
color: #666;
}
}
}
</style>
my-Table组件代码:
<template>
<table class="my-table">
<thead>
<tr>
<slot name="head"></slot><!--具名插槽-->
</tr>
</thead>
<tbody>
<tr v-for="item in data" :key="item.id"><!--循环遍历data中的值,并将id绑定到key属性中-->
<slot name="body" :item="item"></slot><!--通过具名插槽传值-->
</tr>
</tbody>
</table>
</template>
<script>
export default {
props:{
//接收父组件传来的值
data:{
type:Array,
required:true,
}
}
}
</script>
</script>
<style lang="less" scoped>
.my-table {
width: 100%;
border-spacing: 0;
img {
width: 100px;
height: 100px;
object-fit: contain;
vertical-align: middle;
}
th {
background: #f5f5f5;
border-bottom: 2px solid #069;
}
td {
border-bottom: 1px dashed #ccc;
}
td,
th {
text-align: center;
padding: 10px;
transition: all .5s;
&.red {
color: red;
}
}
.none {
height: 100px;
line-height: 100px;
color: #999;
}
}
</style>
App组件代码:
//App组件
<template>
<div class="table-case">
<MyTable :data="goods"><!--父向子传值,子组件props接收-->
<template #head>
<th>编号</th>
<th>图片</th>
<th>名称</th>
<th width="100px">标签</th>
</template>
<template #body="{item}">
<td>{{item.id}}</td>
<td><img :src="item.picture"/> </td>
<td>{{ item.name }}</td>
<td>
<MyTag v-model="item.tag">
</MyTag><!--双向绑定组件的值,子组件向当前父组件传值时直接使用input事件即可被监听到-->
</td>
</template>
</MyTable>
</div>
</template>
<script>
import MyTag from '@/components/MyTag.vue'
import MyTable from '@/components/MyTable.vue'
//mytag 标签组件的封装
//1. 创建组件--初始化
//2. 实现功能
// (1) 双击显示,并且自动聚焦
// v-if v-else @dblclick 操作 isapper
// 自动聚焦:
// 1. $nextTick=>$refs 获取到dom,进行focus获取焦点
// 2. 封装v-focus 全局指令 标签内使用v-focus命令即可
// (2) 失去焦点,隐藏输入框
// (3) 回显标签信息
// 会显的标签信息是父组件传递过来的
// (4) 内容修改了,回车 =>修改标签信息
export default {
components:{
MyTag,
MyTable
},
name: 'TableCase',
data () {
return {
tempText:'茶壶',
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>
<style lang="less" scoped>
.table-case {
width: 1000px;
margin: 50px auto;
img {
width: 100px;
height: 100px;
object-fit: contain;
vertical-align: middle;
}
}
</style>