Vue Tab 组件再探究

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

第一版的 tab 组件

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

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

第二版的 tab 组件

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

<div class="aj-simple-tab-vertical tab" style="padding: 1% 5%;">
		<ul>
			<li :class="{'selected': 0 === selected}" @click="selected = 0">前端文档</li>
			<li :class="{'selected': 1 === selected}" @click="selected = 1">数据库文档</li>
		</ul>
	<div class="content">
		<div :class="{'selected': 0 === selected}">
			<!-- TAB 内容 -->
				<iframe src="${ajaxjsui}/ui-doc"  frameborder="no" width="100%" height="96%"></iframe>
			<!-- // TAB 内容 -->
		</div>
		
		<div :class="{'selected': 1 === selected}">
			<!-- TAB 内容 -->

			<iframe src="${ctx}/admin/DataBaseShowStru"  frameborder="no" width="100%" height="96%"></iframe>
			<!-- // TAB 内容 -->
		</div>
		
		
	</div>
</div>

<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> 组件如下。

// https://vuejs.org/v2/guide/components.html#Content-Distribution-with-Slots
Vue.component('aj-tab', {
   	template: 
   		'<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>',
	props: {
		isVertical : Boolean // 是否垂直方向的布局,默认 false,
	},
    data() {
    	return {
          tabs: [],
          currentTab: {}
        };
    },
    mounted() {
		var arr =  this.$slots.default;
		
		for(var i = 0; i < arr.length; i++) {
			var el = arr[i];
			
			if(el.tag === 'textarea') {
				this.tabs.push({
					name : el.data.attrs['data-title'],
					component: {
		 	            template: '<div>' + el.children[0].text + "</div>"
		   	 	    }
	    		});
	    	}
    	}

    	this.currentTab = this.tabs[0];
    }
});

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

<div class="foo">
	<aj-tab>
		<textarea data-title="Home">Home componentzcc<span class="foo">hihi</span></textarea>
		<textarea data-title="Home2">Home componentzcc2</textarea>
		<textarea data-title="Home3">Home componentzcc3</textarea>
	</aj-tab>
</div>

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

第四版的 tab 组件

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

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

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

<div class="aj-simple-tab-horizontal tab" style="padding: 1% 5%;">
	<ul>
		<li>备份数据</li>
		<li>数据库连接管理</li>
		<li>代码生成器</li>
		<li>后台日志浏览</li>
	</ul>
	<div>
		<div>备份数据</div>
		<div>数据库连接管理</div>
		<div>代码生成器</div>
		<div>后台日志浏览</div>
	</div>
</div>
new Vue({
	el:'.aj-json-form',
	data: {
		scheme : value,
		selected:0
	},
	mounted() {
		var ul = this.$el.querySelector('.aj-simple-tab-horizontal > ul');
		ul.onclick = e => {
			var el = e.target;
			var index = Array.prototype.indexOf.call(el.parentElement.children, el);
			this.selected = index;
		};
		//debugger;
		this.$options.watch.selected.call(this, 0);
	},
	watch: {
		selected(v){
			var headers  = this.$el.querySelectorAll('.aj-simple-tab-horizontal > ul > li');
			var contents = this.$el.querySelectorAll('.aj-simple-tab-horizontal > div > div');
			var each = arr => {							
				for(var i = 0, j = arr.length; i < j; i++) {
					if(v === i) {
						arr[i].classList.add('selected');
					} else {
						arr[i].classList.remove('selected');
					}
				}
			};
			
			each(headers);
			each(headers);
		}
	}
});

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

aj.tab = {
	data(){
		return {
			selected: 0
		};
	},
	mounted() {
		var ul = this.$el.querySelector('.aj-simple-tab-horizontal > ul');
		ul.onclick = e => {
			var el = e.target;
			var index = Array.prototype.indexOf.call(el.parentElement.children, el);
			this.selected = index;
		};
		this.$options.watch.selected.call(this, 0);
	},
	watch: {
		selected(v) {
			var headers  = this.$el.querySelectorAll('.aj-simple-tab-horizontal > ul > li');
			var contents = this.$el.querySelectorAll('.aj-simple-tab-horizontal > div > div');
			var each = arr => {							
				for(var i = 0, j = arr.length; i < j; i++) {
					if(v === i) {
						arr[i].classList.add('selected');
					} else {
						arr[i].classList.remove('selected');
					}
				}
			};
			
			each(headers);
			each(contents);
		}
	}
};

new Vue({
	el:'.aj-json-form',
	data: {
		scheme : value
	},
	mixins: [aj.tab]
});

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

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

发布了301 篇原创文章 · 获赞 264 · 访问量 234万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 成长之路 设计师: Amelia_0503

分享到微信朋友圈

×

扫一扫,手机浏览