零、文章目录
Vue2基础七、ref&nextTick&自定义指令
1、ref
- **作用:**利用 ref 和 $refs 可以用于
获取 dom 元素
, 或组件实例
- **特点:**查找范围 →
当前组件内 (更精确稳定)
,用document.querySelect(‘.box’) 获取的是整个页面中的盒子
(1)获取 dom
- 目标标签 – 添加 ref 属性
<div ref="chartRef">我是渲染图表的容器</div>
- 恰当时机, 通过 this.$refs.xxx, 获取目标标签
mounted () {
console.log(this.$refs.chartRef)
},
(2)获取组件
- 目标组件 – 添加 ref 属性
<BaseForm ref="baseForm"></BaseForm>
- 恰当时机, 通过 this.$refs.xxx, 获取目标组件,就可以调用组件对象里面的方法
this.$refs.baseForm.组件方法()
(3)代码演示
- 安装
echart
包
yarn add echarts
- 父组件
App.vue
<template>
<div class="app">
<div class="base-chart-box">
这是一个捣乱的盒子
</div>
<BaseChart></BaseChart>
</div>
</template>
<script>
import BaseChart from './components/BaseChart.vue'
export default {
components:{
BaseChart
}
}
</script>
<style>
.base-chart-box {
width: 300px;
height: 200px;
}
</style>
- 子组件
BaseChart.vue
<template>
<div class="base-chart-box" ref="baseChartBox">子组件</div>
</template>
<script>
import * as echarts from 'echarts'
export default {
mounted() {
// 基于准备好的dom,初始化echarts实例
// document.querySelector 会查找项目中所有的元素
// $refs只会在当前组件查找盒子
// var myChart = echarts.init(document.querySelector('.base-chart-box'))
var myChart = echarts.init(this.$refs.baseChartBox)
// 绘制图表
myChart.setOption({
title: {
text: 'ECharts 入门示例',
},
tooltip: {},
xAxis: {
data: ['衬衫', '羊毛衫', '雪纺衫', '裤子', '高跟鞋', '袜子'],
},
yAxis: {},
series: [
{
name: '销量',
type: 'bar',
data: [5, 20, 36, 10, 10, 20],
},
],
})
},
}
</script>
<style scoped>
.base-chart-box {
width: 400px;
height: 300px;
border: 3px solid #000;
border-radius: 6px;
}
</style>
- 效果
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WmBy90dR-1690447474327)(https://bluecusliyoupicrep.oss-cn-hangzhou.aliyuncs.com/202307131641861.png)]
2、nextTick
(1)异步更新问题
-
需求:编辑标题, 编辑框自动聚焦
- 点击编辑,显示编辑框
- 让编辑框,立刻获取焦点
this.isShowEdit = true // 显示输入框 this.$refs.inp.focus() // 获取焦点
-
问题:“显示之后”,立刻获取焦点是不能成功的!
-
原因:
Vue 是 异步更新 DOM (提升性能)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1BdzLqJY-1690447474329)(https://bluecusliyoupicrep.oss-cn-hangzhou.aliyuncs.com/202307131654743.png)]
(2)$nextTick解决问题
- $nextTick:
等 DOM 更新后
, 才会触发执行此方法里的函数体 - 语法:
this.$nextTick(函数体)
this.$nextTick(() => {
this.$refs.inp.focus()
})
- 注意:
$nextTick
内的函数体 一定是箭头函数
,这样才能让函数内部的this指向Vue实例
(3)代码实现
<template>
<div class="app">
<div v-if="isShowEdit">
<input type="text" v-model="editValue" ref="inp" />
<button>确认</button>
</div>
<div v-else>
<span>{{ title }}</span>
<button @click="editFn">编辑</button>
</div>
</div>
</template>
<script>
export default {
data() {
return {
title: '大标题',
isShowEdit: false,
editValue: '',
}
},
methods: {
editFn() {
// 显示输入框
this.isShowEdit = true
// // 获取焦点
// this.$refs.inp.focus()
this.$nextTick(() => {
this.$refs.inp.focus()
})
} },
}
</script>
3、自定义指令
(1)指令分类
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YJamBXtu-1690447474330)(https://bluecusliyoupicrep.oss-cn-hangzhou.aliyuncs.com/202307141042338.png)]
(2)自定义指令
-
自定义指令:自己定义的指令, 可以
封装一些 dom 操作
, 扩展额外功能 -
自定义指令语法:
- 全局注册
//在main.js中 // 1. 全局注册指令 Vue.directive('focus', { // inserted 会在 指令所在的元素,被插入到页面中时触发 inserted (el) { // el 就是指令所绑定的元素 // console.log(el); el.focus() } })
- 局部注册
//在Vue组件的配置项中 directives: { "指令名": { inserted () { // 可以对 el 标签,扩展额外功能 el.focus() } } }
-
使用指令
- 在使用指令的时候,一定要
先注册,再使用
,否则会报错 - 使用指令语法:
v-指令名
。如:<input type="text" v-focus/>
注册
指令时不用加v-前缀
,使用
时要加v-前缀
<input v-focus type="text">
- 在使用指令的时候,一定要
-
指令中的配置项介绍
inserted:
被绑定元素插入父节点时调用的钩子函数el:
使用指令的那个DOM元素
-
完整代码实现:
- 需求:当页面加载时,让元素获取焦点(autofocus在safari浏览器有兼容性)
<template>
<div>
<h1>自定义指令</h1>
<input v-focus type="text">
</div>
</template>
<script>
export default {
// 2. 局部注册指令
directives: {
// 指令名:指令的配置项
focus: {
inserted (el) {
el.focus()
}
}
}
}
</script>
<style>
</style>
(3)自定义指令的值
-
需求:实现一个 color 指令 - 传入不同的颜色, 给标签设置文字颜色
-
语法:
v-指令名 = "指令值"
,通过等号
可以绑定指令的值
<div v-color="color">我是内容</div>
- 通过
binding.value
可以拿到指令值,通过update 钩子
,可以监听指令值的变化,进行dom更新操作
directives: { color: { inserted (el, binding) { el.style.color = binding.value }, update (el, binding) { el.style.color = binding.value } } }
-
完整代码实现:
<template>
<div>
<h1 v-color="color1">指令的值1测试</h1>
<h1 v-color="color2">指令的值2测试</h1>
</div>
</template>
<script>
export default {
data () {
return {
color1: 'red',
color2: 'orange'
}
},
directives: {
color: {
// 1. inserted 提供的是元素被添加到页面中时的逻辑
inserted (el, binding) {
// console.log(el, binding.value);
// binding.value 就是指令的值
el.style.color = binding.value
},
// 2. update 指令的值修改的时候触发,提供值变化后,dom更新的逻辑
update (el, binding) {
console.log('指令的值修改了');
el.style.color = binding.value
}
}
}
}
</script>
<style>
</style>
(4)v-loading 指令封装
- 场景:实际开发过程中,发送
请求需要时间
,在请求的数据未回来时,页面会处于空白状态
=>用户体验不好
- 需求:封装一个 v-loading 指令,实现加载中的效果
-
分析:
- 本质 loading 效果就是一个蒙层,盖在了盒子上
- 数据请求中,开启loading状态,添加蒙层
- 数据请求完毕,关闭loading状态,移除蒙层
-
实现思路:
- 准备一个 loading 类,通过伪元素定位,设置宽高,实现蒙层
- 开启关闭 loading 状态(添加移除蒙层),本质只需要添加移除类即可
- 结合自定义指令的语法进行封装复用
-
代码实现:
<template>
<div class="main">
<div class="box" v-loading="isLoading">
<ul>
<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="./assets/img/news.png" alt="">
</div>
</li>
</ul>
</div>
</div>
</template>
<script>
export default {
data () {
return {
list: [],
isLoading: true
}
},
created () {
// 更新到 list 中,用于页面渲染 v-for
this.list = [
{
id:1,
title:'testtitle1',
source:'testsource1',
time:'testtime1'
},
{
id:2,
title:'testtitle2',
source:'testsource2',
time:'testtime2'
}]
//三秒后loading移除
setTimeout(()=>{
this.isLoading = false
}, 3000);
},
directives: {
loading: {
inserted (el, binding) {
binding.value ? el.classList.add('loading') : el.classList.remove('loading')
},
update (el, binding) {
binding.value ? el.classList.add('loading') : el.classList.remove('loading')
}
}
}
}
</script>
<style>
.loading:before {
content: '';
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
background: #fff url('./assets/img/loading.gif') no-repeat center;
}
.box2 {
width: 400px;
height: 400px;
border: 2px solid #000;
position: relative;
}
.box {
width: 800px;
min-height: 500px;
border: 3px solid orange;
border-radius: 5px;
position: relative;
}
.news {
display: flex;
height: 120px;
width: 600px;
margin: 0 auto;
padding: 20px 0;
cursor: pointer;
}
.news .left {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
padding-right: 10px;
}
.news .left .title {
font-size: 20px;
}
.news .left .info {
color: #999999;
}
.news .left .info span {
margin-right: 20px;
}
.news .right {
width: 160px;
height: 120px;
}
.news .right img {
width: 100%;
height: 100%;
object-fit: cover;
}
</style>