选项卡切换其实在项目中是一个比较常见的功能,我们平时开发中大部分是这样来实现的:
<template>
<div class="swiper">
<div class="tab-container">
<div
:class="['tab-item', { 'tab-active': currentIndex == index }]"
v-for="(item, index) in tabData"
:key="index"
@click="tabClick(index)"
>
{{ item }}
</div>
</div>
<div
class="tab-item"
v-for="(item, index) in tabData"
:key="index"
@click="tabClick(index)"
>
{{ item }}
</div>
</div>
</div>
</template>
<script>
export default {
components: {},
data() {
return {
tabData: ["选项一", "选项二", "选项三"],
currentIndex: 0,
};
},
methods: {
tabClick(index) {
this.currentIndex = index;
},
},
};
</script>
<style scoped>
.tab-container {
width: 300px;
height: 50px;
border: 1px solid #000;
display: flex;
}
.tab-item {
flex: 1;
height: 50px;
text-align: center;
line-height: 50px;
cursor: pointer;
}
.tab-active {
color: #fff;
background: #000;
}
</style>
也就是把当前点击的currentIndex
与列表中的index
对比,通过:class="['tab-item', { 'tab-active': currentIndex == index }]"
动态控制类名,从而达到切换的效果。那么有没有其他的实现方法呢?接下来使用自定义指令来实现一下,其实也非常简单。
自定义指令
引用官网上的一句话:
在 Vue2.0 中,代码复用和抽象的主要形式是组件。然而,有的情况下,你仍然需要对普通 DOM 元素进行底层操作,这时候就会用到自定义指令。
vue里也提供一些内置的指令v-for
、v-if
、v-show
,v-model
等等,像v-for
的底层就是用forEach
这样的方法去遍历每个元素节点并动态往里面添加值,当然也可能不是用的forEach
方法,所以说白了,自定义指令除了可以实现功能的复用,实际上里面就是让我们去操作dom元素的。
自定义指令里面有两个比较重要的钩子函数,也是我们即将要用到的两个钩子:bind和update
-
bind
:只调用一次,指令第一次绑定到元素时调用。 -
update
:被绑定元素的状态发生改变时调用
它们两个钩子函数会被传入相同的参数:el
、binding
、vnode
、oldVnode
,现在我们只需要前两个参数即可。
选项卡切换
首先先在src下建立directives文件夹,然后建立navCurrent.js文件
在App.vue页面中引入并注册自定义指令
做完这些后我们首先捋一下思路,在bind
和update
中可以获取到指令绑定的元素(el)以及指令的一些信息(binding),我们传递给自定义指令的参数就放在了binding.value
中,回到刚才的代码段:
<template>
//给自定义指令传递了一个对象参数,这里可进行参数的配置
<div
class="tab-container"
v-nav-current="{
currentIndex,
className: 'tab-item',
activeName: 'tab-active',
}"
>
<div
class="tab-item"
v-for="(item, index) in tabData"
:key="index"
@click="tabClick(index)"
>
{{ item }}
</div>
</div>
</div>
</template>
为什么要把指令绑定到父元素上呢,是因为这样我们就可以获取到该元素下的所有子元素,然后传递一个currentIndex
也就是当前点击的下标,所有子元素中的第currentIndex
项就是要选中的项,其次把默认样式以及选种样式的类名也传递过去。
export default {
bind(el, binding, vnode, oldVnode) {
// 获取传递过来的参数
const opt = binding.value;
// 获取指令所绑定元素下的所有子元素
const childDom = el.getElementsByClassName(opt.className);
// 为当前选中的元素添加选中的类名
childDom[opt.currentIndex].className += ` ${opt.activeName}`;
}
};
这样就出现了开头那一幕
因为当前的currentIndex
是0,所以默认第一项是选中状态
当点击其他选项时(对视图进行了操作)会触发自定义指令的update
函数,然后里面还是相同的逻辑
export default {
bind(el, binding, vnode, oldVnode) {
// 获取传递过来的参数
const opt = binding.value;
// 获取指令所绑定元素下的所有子元素
const childDom = el.getElementsByClassName(opt.className);
// 为当前选中的元素添加选中的类名
childDom[opt.currentIndex].className += ` ${opt.activeName}`;
}
update(el, binding, vnode) {
const opt = binding.value;
const childDom = el.getElementsByClassName(opt.className);
childDom[opt.currentIndex].className += ` ${opt.activeName}`;
}
};
这时我们会发现每选择一个选项之前的选中状态不会消失,这就又涉及到update
的另一个知识点
在update
函数中的binding
参数中有一个oldValue
字段,它表示的是上一次更新时的值,我们就可以利用它来清除上一次的选中状态,我们在对上面的代码进行改造:
export default {
bind(el, binding, vnode, oldVnode) {
// 获取传递过来的参数
const opt = binding.value;
// 获取指令所绑定元素下的所有子元素
const childDom = el.getElementsByClassName(opt.className);
// 为当前选中的元素添加选中的类名
childDom[opt.currentIndex].className += ` ${opt.activeName}`;
}
update(el, binding, vnode) {
const opt = binding.value;
const childDom = el.getElementsByClassName(opt.className);
// 清除上一次的选中状态
childDom[
binding.oldValue.currentIndex
].className = `${binding.oldValue.className}`; //或者是`${opt.className}`
childDom[opt.currentIndex].className += ` ${opt.activeName}`;
}
};
此时就可以进行正常切换啦!
这样做可以说体现了一种复用的思想,遇到同样的功能时直接给指令传入对应的参数值即可实现,跟直接动态修改class类名相比代码量上虽然说没有太大改变,但也是一种思想不是么?
end...