选项卡切换——自定义指令(vue2.0)

155 阅读3分钟

img(1).png

选项卡切换其实在项目中是一个比较常见的功能,我们平时开发中大部分是这样来实现的:

<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-forv-ifv-show,v-model等等,像v-for的底层就是用forEach这样的方法去遍历每个元素节点并动态往里面添加值,当然也可能不是用的forEach方法,所以说白了,自定义指令除了可以实现功能的复用,实际上里面就是让我们去操作dom元素的。

自定义指令里面有两个比较重要的钩子函数,也是我们即将要用到的两个钩子:bind和update

  • bind:只调用一次,指令第一次绑定到元素时调用。

  • update:被绑定元素的状态发生改变时调用

它们两个钩子函数会被传入相同的参数:elbindingvnodeoldVnode,现在我们只需要前两个参数即可。

选项卡切换

首先先在src下建立directives文件夹,然后建立navCurrent.js文件

img2(1).png

在App.vue页面中引入并注册自定义指令

img3(1).png

做完这些后我们首先捋一下思路,在bindupdate中可以获取到指令绑定的元素(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>

img4(1).png

为什么要把指令绑定到父元素上呢,是因为这样我们就可以获取到该元素下的所有子元素,然后传递一个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}`;
    }
  };

这样就出现了开头那一幕

img(1).png

因为当前的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}`;
    }
  };

1674879963(1).png

这时我们会发现每选择一个选项之前的选中状态不会消失,这就又涉及到update的另一个知识点

1674880141(1).png

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...