认识ref引用
3句话简单介绍ref是个什么鬼
-
地球人都知道,Vue.js模板当中,既可以写Vue组件,也可以写具体的HTML标签。
-
同时,Vue提供了一个特殊的
ref属性来对普通元素或组件建立引用。<template> <div class="page"> <div class="img-container"> <!-- 普通HTML --> <ul ref="imgListElm"> <li><img src=""></li> </ul> </div> <!-- Vue组件 --> <van-button type="primary" ref="imgBtn">主要按钮</van-button> </div> </template> -
如果
ref定义在HTML标签,那么引用的就是渲染后具体的DOM对象;如果定义在Vue组件上,则引用组件的Vue实例,进而获取Vue实例内的data数据,或者调用methods方法。
Vue2.x与Vue3.x使用ref的区别
在视图定义了ref引用后,Vue在编译期间就会自动建立绑定关系,把ref引用挂载到Vue组件实例中,我们在<script>代码中就能直接使用了,但Vue2与Vue3的使用是有些许区别的:
- Vue2.x将定义的所有
ref引用,自动归集在this.$refs集合中。
export default {
mounted(){
// 普通DOM对象
let $imgListElm = this.$refs.imgListElm;
console.log($imgListElm.children)
// Vue组件对象
let imgBtn = this.$refs.imgBtn;
imgBtn.primary = 'warning'
}
}
- Vue3.x需要我们手动把
ref引用,赋值到变量。
// composition API风格
// 提前要把用到的接口,都先import进来
import { ref, onMounted } from 'vue'
export default {
setup(){
// 使用ref()响应式API,定义同名的变量接收ref引用
const $imgListElm = ref(null)
const imgBtn = ref(null)
onMounted(() => {
// 普通DOM对象
console.log($imgListElm.children)
// Vue组件对象
imgBtn.primary = 'warning'
})
// 记得return要把变量都暴露出去
return {
$imgListElm,
imgBtn
}
}
}
- 除此之外,还 允许以
:ref的方式进行动态的绑定引用。 当然,不一定非要用ref()来接收,也可以使用reactive()来接收一个键值对的集合,这样的话,我们就可以像当初通过id操作jQuery对象的方式一样,直接获取到具体的DOM对象。
<template>
<ul>
<!-- 动态ref属性的值是一个JS执行函数,就是对引用进行 赋值 -->
<li v-for="user in list"
:ref="el => { if (el) userRefMap['ref_' + user.oid] = el }">
{{ user.name }}
</li>
</ul>
</template>
<script>
import { reactive, toRefs } from 'vue'
export default {
setup() {
// 响应数据
const state = reactive({
// ref映射对象
userRefMap: {},
// user数组
list: [
{
oid: 1001,
name: 'huilin'
},
{
oid: 1002,
name: 'fafa'
},
{
oid: 1003,
name: 'qiqi'
}
]
})
onMounted(() => {
// 输出DOM
console.log(state.userRefMap['ref_1001'])
})
return {
...toRefs(state),
}
}
}
</script>
ref模板引用的案例实践
实现效果
选中菜品后,进度条渐进加载,2秒后完成确认。同时,允许随时取消选中,再重新进行划菜。
主要思路
- 菜品
<div class="dish-item">下,要有个<div class="checked-mask"/>遮罩层。通过改变遮罩层的样式style="width: 100%",同时设置transition过渡动画,实现渐进加载效果。 - click监听事件中,通过
ref引用来操作遮罩层DOM对象。
这里可能有人会问:为什么不直接设置动态的:id属性,以document.getElementById()来操作DOM?
这是因为JS和DOM是彼此不相通的孤岛,每次访问DOM API都要过一次桥,频繁的过桥会有性能损耗,既然Vue提供了ref的方式,我们尽量还是遵循这个游戏规则
核心代码
<div v-for="dish in dishList"
:key="dish.oid"
:class="{checked : dish.submitted != -1}"
@click="dishSubmit(dish)" class="dish-item">
<!-- 遮罩层 -->
<div :ref="el => { if(el) dishElmMap['id_' + dish.oid] = el}"
class="checked-mask"
v-show="dish.submitted != -1"></div>
<!-- 2秒后确认的icon -->
<van-icon :style="{visibility: dish.submitted == 1 ? 'visible' : 'hidden', opacity: dish.submitted == 1 ? 1 : 0}"
name="checked"
class="checked-icon"
size="30" />
<!-- 菜品名称 -->
<span class="name text-ellipsis">{{dish.name}}</span>
<!-- 客户备注 -->
<span class="tip text-ellipsis" style="text-align: right;">{{dish.remark}}</span>
</div>
import { reactive, toRefs, nextTick } from 'vue'
export default {
name: 'Order',
setup() {
// 响应数据
let state = reactive({
dishElmMap: {},
dishList: [{
name: '干贝苦瓜羹',
remark: '苦瓜熬久一点',
submitted: -1,
percent: 0,
oid: 332312
},
{
name: '周麻婆泡菜鱼',
remark: null,
submitted: -1,
percent: 0,
oid: 332313
},
{
name: '酸辣土豆丝',
remark: '变态辣',
submitted: -1,
percent: 0,
oid: 332314
},
{
name: '蒜蓉粉丝金针菇',
remark: null,
submitted: -1,
percent: 0,
oid: 332315
},
{
name: '飘香馋嘴蛙',
remark: null,
submitted: -1,
percent: 0,
oid: 332316
},
{
name: '麻婆田鸡王',
remark: null,
submitted: -1,
percent: 0,
oid: 332317
},
{
name: '怀旧水煮鱼',
remark: null,
submitted: -1,
percent: 0,
oid: 332318
},
{
name: '一品香脆虾',
remark: null,
submitted: -1,
percent: 0,
oid: 332319
}
]
}]
// 菜品提交
let dishSubmit = (dish) => {
// 获取具体的DOM
let elm = state.dishElmMap['id_' + dish.oid]
if (dish.submitted === -1) {
// 要选中时
// -1未划菜;0-划菜中;1-已确认划菜
dish.submitted = 0
// 设置DOM.style.width = 100%
nextTick(() => {
dish.animateTimer = setTimeout(() => {
elm.style.width = '100%';
},500)
})
if(dish.timer){
clearTimeout(dish.timer)
clearTimeout(dish.animateTimer)
}
// 3秒后修改状态为已划菜
dish.timer = setTimeout(() => {
dish.submitted = 1
clearTimeout(dish.timer)
clearTimeout(dish.animateTimer)
}, 2000)
} else {
// 其他情况回退到初始状态
dish.submitted = -1
nextTick(() => {
setTimeout(() => {
elm.style.width = '0%';
},0)
})
clearTimeout(dish.timer)
clearTimeout(dish.animateTimer)
}
}
}
}