Vue Tab 组件再探究

女爷i 2023-07-16 09:56 62阅读 0赞

初学 Vue 的时候,发现用 Vue 来写 Tab 组件是如此简单,利用“数据驱动”的思路还真和 js 控制 dom 不一样。请见下面第一版的代码,没有 js dom 那样 for 遍历各元素控制显示或隐藏,而是用 {'selected': index === selected} 控制样式,非常简洁。

第一版的 tab 组件

  1. // 简单选项卡
  2. Vue.component('aj-simple-tab', {
  3. template :
  4. '<div :class="isVertical ? \'aj-simple-tab-vertical\' : \'aj-simple-tab-horizontal\' ">\ <ul>\ <li v-for="(item, index) in items" :class="{\'selected\': index === selected}" @click="changeTab(index);">{ {item.name}}</li>\ </ul>\ <div class="content">\ <div v-for="(item, index) in items" :class="{\'selected\': index === selected}" v-html="item.content"></div>\ </div>\ </div>',
  5. props: {
  6. isVertical : Boolean, // 是否垂直方向的布局,默认 false,
  7. initItems : Array
  8. },
  9. data() {
  10. return {
  11. selected : 0,
  12. items : this.initItems || [
  13. { name : '杜甫:望岳', content : '岱宗夫如何,齊魯青未了。<br>\ 造化鐘神秀,陰陽割昏曉。<br>\ 蕩胸生層云,決眥入歸鳥,<br>\ 會當凌絕頂,一覽眾山小。'
  14. },
  15. { name : '资质证照', content : '之所以如此,端在于中国传统中存在着发达的契约。予谓不信,可看看早年由福建师范大学内部印行的两册本《明清福建经济契约文书选集》,中国社会科学出版社出版的《自贡盐业契约档案选集》,花山文艺出版社出版的共二十册的《徽州千年契约文书》;再看看近些年由安徽师范大学出版社出版的十卷本的《千年徽州契约文书集萃》,广西师范大学出版社出版的四辑共四十册《徽州文书》、三辑共三十册的《清水江文书》,民族出版社出版的多卷本《贵州清水江流域明清契约文书》,凤凰出版社出版的《敦煌契约文书辑校》,天津古籍出版社出版的《清代宁波契约文书辑校》,浙江大学出版社出版的《清代浙东契约文书辑选》……不难发现,中国传统契约文书的整理出版,完全可称为如雨后春笋般在迅速成长!'},
  16. { name : '资质证照', content : '笔者出于个人兴趣,在关注这些已经整理出版的、卷帙浩繁的契约文献的同时,也游走各地,或亲自搜集各类契约文书,或到一些地方档案馆查看其业已编辑成册、内部印行的传统契约文书,如在台湾宜兰、高雄,山东青岛、威海,贵州锦屏、从江等地档案机构或民间都见到过相关契约文献。记忆尤深的一次,是我和几位学生共游山东浮来山。在一处值班室里,居然发现有人以清代契约文本粘糊墙壁!足见只要我们稍加留意,在这个文明发展历经数千年的国度,不时可以发现一些令人称心的古代契约文献。'}
  17. ]
  18. };
  19. },
  20. methods : {
  21. changeTab(index) {
  22. this.selected = index;
  23. }
  24. }
  25. });

演示的例子如下图。
在这里插入图片描述
该例子的 items 数据项还提供了默认的演示数据,不过问题是,tab 的内容要写在 js 里面,是比较麻烦的,能不能改写在标签里面呢?

第二版的 tab 组件

这问题一时还没有好的办法,因为碍于组件的关系,输入的数据还是变成 js 字符串,那样就涉及转义等的问题,处理起来也不方便。于是干脆放弃 vue 组件的概念,直接写标签然后用最简单的 vue 实例化方式。

  1. <div class="aj-simple-tab-vertical tab" style="padding: 1% 5%;">
  2. <ul>
  3. <li :class="{ 'selected': 0 === selected}" @click="selected = 0">前端文档</li>
  4. <li :class="{ 'selected': 1 === selected}" @click="selected = 1">数据库文档</li>
  5. </ul>
  6. <div class="content">
  7. <div :class="{ 'selected': 0 === selected}">
  8. <!-- TAB 内容 -->
  9. <iframe src="${ajaxjsui}/ui-doc" frameborder="no" width="100%" height="96%"></iframe>
  10. <!-- // TAB 内容 -->
  11. </div>
  12. <div :class="{ 'selected': 1 === selected}">
  13. <!-- TAB 内容 -->
  14. <iframe src="${ctx}/admin/DataBaseShowStru" frameborder="no" width="100%" height="96%"></iframe>
  15. <!-- // TAB 内容 -->
  16. </div>
  17. </div>
  18. </div>
  19. <script> TAB = new Vue({ el : '.tab', data: { selected:0 } }); </script>

这样的方式比第一版跟简单,完全没有了 for 循环,都是一个个写死 0、1、2……的判断。虽然很脑残,但对于初学者来说这足够浅显的。放入如果 tab 数量不多,几个写死还是问题的不大的。

第三版的 tab 组件

第二版的方式只是玩玩,肯定还是要自动处理 tab 的。想到 vue 支持 slot 机制,也就是标签嵌套标签,对于不确定的内容,使用 slot 非常灵活。基于这一思路再次封装一 tab 组件,既解决 HTML 定义 tab 内容的需求,也能通过 vue 组件来封装 tab 逻辑,于是形成 <aj-tab> 组件如下。

  1. // https://vuejs.org/v2/guide/components.html#Content-Distribution-with-Slots
  2. Vue.component('aj-tab', {
  3. template:
  4. '<div :class="isVertical ? \'aj-simple-tab-vertical\' : \'aj-simple-tab-horizontal\' ">\ <button v-for="tab in tabs" v-bind:key="tab.name"\ v-bind:class="[\'tab-button\', { active: currentTab.name === tab.name }]"\ v-on:click="currentTab = tab">{ {tab.name}}\ </button>\ <component v-bind:is="currentTab.component" class="tab"></component>\ </div>',
  5. props: {
  6. isVertical : Boolean // 是否垂直方向的布局,默认 false,
  7. },
  8. data() {
  9. return {
  10. tabs: [],
  11. currentTab: { }
  12. };
  13. },
  14. mounted() {
  15. var arr = this.$slots.default;
  16. for(var i = 0; i < arr.length; i++) {
  17. var el = arr[i];
  18. if(el.tag === 'textarea') {
  19. this.tabs.push({
  20. name : el.data.attrs['data-title'],
  21. component: {
  22. template: '<div>' + el.children[0].text + "</div>"
  23. }
  24. });
  25. }
  26. }
  27. this.currentTab = this.tabs[0];
  28. }
  29. });

第三版使用了 <conmponent> 动态组件,现在想了下,其实应该可以优化而不用动态组件的。调用方式如下。

  1. <div class="foo">
  2. <aj-tab>
  3. <textarea data-title="Home">Home componentzcc<span class="foo">hihi</span></textarea>
  4. <textarea data-title="Home2">Home componentzcc2</textarea>
  5. <textarea data-title="Home3">Home componentzcc3</textarea>
  6. </aj-tab>
  7. </div>

调用方式利用了 <textarea>,避免了浏览器渲染 HTML。标题安排在 data-title 属性上。

第四版的 tab 组件

本来写到第三版,其实已经足够 ok 的了,但担心 tab 里面不能放置 vue 组件,于是又另辟蹊径搞了第四版的 tab,实际上第三版是可以的,我对 <textarea> 多虑了。

第四版却是放弃 vue 数据状态的控制,改用原始的 for 循环控制。

调用方式如下,先声明 HTML,然后 new Vue 实例化(还没使用组件封装之)。

  1. <div class="aj-simple-tab-horizontal tab" style="padding: 1% 5%;">
  2. <ul>
  3. <li>备份数据</li>
  4. <li>数据库连接管理</li>
  5. <li>代码生成器</li>
  6. <li>后台日志浏览</li>
  7. </ul>
  8. <div>
  9. <div>备份数据</div>
  10. <div>数据库连接管理</div>
  11. <div>代码生成器</div>
  12. <div>后台日志浏览</div>
  13. </div>
  14. </div>
  15. new Vue({
  16. el:'.aj-json-form',
  17. data: {
  18. scheme : value,
  19. selected:0
  20. },
  21. mounted() {
  22. var ul = this.$el.querySelector('.aj-simple-tab-horizontal > ul');
  23. ul.onclick = e => {
  24. var el = e.target;
  25. var index = Array.prototype.indexOf.call(el.parentElement.children, el);
  26. this.selected = index;
  27. };
  28. //debugger;
  29. this.$options.watch.selected.call(this, 0);
  30. },
  31. watch: {
  32. selected(v){
  33. var headers = this.$el.querySelectorAll('.aj-simple-tab-horizontal > ul > li');
  34. var contents = this.$el.querySelectorAll('.aj-simple-tab-horizontal > div > div');
  35. var each = arr => {
  36. for(var i = 0, j = arr.length; i < j; i++) {
  37. if(v === i) {
  38. arr[i].classList.add('selected');
  39. } else {
  40. arr[i].classList.remove('selected');
  41. }
  42. }
  43. };
  44. each(headers);
  45. each(headers);
  46. }
  47. }
  48. });

那么接着封装成组件吧?不过稍等,能不能用多态 mixins 特性呢?可以的,且看……

  1. aj.tab = {
  2. data(){
  3. return {
  4. selected: 0
  5. };
  6. },
  7. mounted() {
  8. var ul = this.$el.querySelector('.aj-simple-tab-horizontal > ul');
  9. ul.onclick = e => {
  10. var el = e.target;
  11. var index = Array.prototype.indexOf.call(el.parentElement.children, el);
  12. this.selected = index;
  13. };
  14. this.$options.watch.selected.call(this, 0);
  15. },
  16. watch: {
  17. selected(v) {
  18. var headers = this.$el.querySelectorAll('.aj-simple-tab-horizontal > ul > li');
  19. var contents = this.$el.querySelectorAll('.aj-simple-tab-horizontal > div > div');
  20. var each = arr => {
  21. for(var i = 0, j = arr.length; i < j; i++) {
  22. if(v === i) {
  23. arr[i].classList.add('selected');
  24. } else {
  25. arr[i].classList.remove('selected');
  26. }
  27. }
  28. };
  29. each(headers);
  30. each(contents);
  31. }
  32. }
  33. };
  34. new Vue({
  35. el:'.aj-json-form',
  36. data: {
  37. scheme : value
  38. },
  39. mixins: [aj.tab]
  40. });

用法就是上面的例子了,注意 mixins: [aj.tab] 这里。

小小的 tab,竟然重构四次之多~哈哈哈,我都佩服我自己的孜孜不倦精神……

发表评论

表情:
评论列表 (有 0 条评论,62人围观)

还没有评论,来说两句吧...

相关阅读

    相关 Vue Tab 探究

    初学 Vue 的时候,发现用 Vue 来写 Tab 组件是如此简单,利用“数据驱动”的思路还真和 js 控制 dom 不一样。请见下面第一版的代码,没有 js dom 那样 f

    相关 Vue

    Vue组件 1. 什么是组件 1. 组件的概念:组件即自定义控件,是Vue.js最强大的功能之一 2. 组件的用途:组件能够封装可重用代码,扩展html标签功能