前言

在Vue还未流行的时候,我们都是用JQuery来封装一个选项卡插件,如今Vue当道,让我们一起来看看从JQ时代过来的前端是如何转换思路,用数据驱动DOM的思想打造一个Vue选项卡组件。

接下来,正文从这开始~

先来看一下用Vue写的选项卡组件在浏览器上的展示效果:

其实,你在浏览器上看到的UI界面效果也就是那么回事,中规中矩。当点开Chrome的Devtools下面的Elements选项,你看到的dom节点其实和jQuery的dom节点如出一辙,不同的是,现在你看到的dom树是在Vue组件生命周期mounted之后渲染得到的真实DOM

首先我们来聊一聊关于选项卡组件的业务需求,每个标签页的主体内容是由使用组件的父级控制,所以这部分应该是一个slot。slot的数量决定了标签切换按钮的数量。假设我们有3个标签页,点击每个标签按钮时,另外的两个标签对应的slot将被隐藏掉。

先来贴出html结构:

  1. <div id="app" v-cloak>
  2. <tabs>
  3. <pane label="标签一" name="1">
  4. <p>标签一的内容</p>
  5. </pane>
  6. <pane label="标签二" name="2">
  7. <p>标签二的内容</p>
  8. </pane>
  9. <pane label="标签三" name="3">
  10. <p>标签三的内容</p>
  11. </pane>
  12. </tabs>
  13. </div>

由html结构可知,我们需要定义两个组件tabs和子组件pane,pane嵌套在标签组件tabs里面。由于tabs和pane两个组件是分离的,但是tabs的组件上的标题应该由pane组件来定义,因为slot是写在pane里,因此在组件初始化(以及标签标题动态改变)时,tabs要从pane里获取标题,并保存起来,自己使用。

接下来,先初始化各个组件:

  1. Vue.component('tabs',{
  2. template:`
  3. <div class="tabs">
  4. <div class="tabs-bar">
  5. <!-- 标签页标题,这里要用v-for -->
  6. </div>
  7. <div class="tabs-content">
  8. <!-- 这里的slot就是嵌套的pane -->
  9. <slot></slot>
  10. </div>
  11. </div>
  12. `
  13. })
  1. Vue.component('pane',{
  2. name:'pane',
  3. template:`
  4. <div class="pane" v-show="show">
  5. <slot></slot>
  6. </div>
  7. `,
  8. data:function(){
  9. return {
  10. show:true
  11. }
  12. }
  13. })

pane组件中需要控制标签页内容的显示与隐藏,所以设置一个data:show,并且用v-show指令来控制元素。

如果你是用jQuery写过选项卡,应该知道,在写完控制tabs标题显示与隐藏后,接下来该控制tabs内容的显示与隐藏了。那么如果要点击对应的标签页标题显示对应的标签页内容,此时应该有一个唯一的值来标识这个pane,我们可以设置一个prop:name让用户来设置,其实这个name也就是索引index。除了name,还需要设置标签页的标题prop:label。

  1. props:{
  2. name:{
  3. type:String
  4. },
  5. label:{
  6. type:String,
  7. default:''
  8. }
  9. }

上面的标题prop:label 用户是可以动态调整的,所以在pane初始化及label更新时,都要通知父组件也更新,因为是独立独立组件,我们可以直接通过this.$parent访问tabs组件的实例来调用它的方法更新标题,这个方法名暂定为updateNav:

  1. methods:{
  2. updateNav(){
  3. this.$parent.updateNav();
  4. }
  5. },
  6. watch:{
  7. label(){
  8. this.updateNav();
  9. }
  10. },
  11. mounted(){
  12. this.updateNav();
  13. }

通过以上代码可以看到,在生命周期mounted,也就是pane初始化时,调用一遍tabs的updateNav方法,同时监听了prop:label,在label更新时,同样调用。

说完了pane组件,剩下的任务就是完成tabs组件。

首先就是需要把pane组件设置的标题动态渲染出来,也就是当pane触发tabs的updateNav方法时,更新标题内容。

  1. Vue.component('tabs',{
  2. template:`
  3. <div class="tabs">
  4. <div class="tabs-bar">
  5. <div
  6. :class="tabCls(item)"
  7. v-for="(item,index) in navList"
  8. @click="handleChange(index)">
  9. {{item.label}}
  10. </div>
  11. </div>
  12. <div class="tabs-content">
  13. <slot></slot>
  14. </div>
  15. </div>
  16. `,
  17. props:{
              // 这里的value是为了可以使用v-model
  18. value:{
  19. type:[String, Number]
  20. }
  21. },
  22. data:function(){
  23. return {
                // 因为不能修改value,所以复制一份自己维护
  24. currentValue:this.value,
  25. navList:[] // 用于渲染tabs的标题
  26. }
  27. },
  28. methods:{
  29. tabCls:function(item){
  30. return [
  31. 'tabs-tab',
  32. {
                     // 给当前选中的tab加一个class
  33. 'tabs-tab-active': item.name === this.currentValue
  34. }
  35. ]
  36. },
  37. getTabs(){
  38. // 通过遍历子组件,得到所有的pane组件
  39. return this.$children.filter(function(item){
  40. return item.$options.name === 'pane';
  41. });
  42. },
  43. updateNav(){
  44. this.navList=[];
  45. // 设置对this的引用,在function回调里,this指向的并不是vue实例
  46. var _this = this;
  47.  
  48. this.getTabs().forEach(function(pane,index){
  49. _this.navList.push({
  50. label:pane.label,
  51. name:pane.name || index
  52. });
  53. // 如果没有给pane设置那么,默认设置它的索引
  54. if(!pane.name) pane.name = index;
  55. // 设置当前选中的tab的索引
  56. if(index === 0){
  57. if(!_this.currentValue){
  58. _this.currentValue = pane.name || index;
  59. }
  60. }
  61. });
  62. // console.log(this.navList);
  63.  
  64. this.updateStatus();
  65. },
  66. updateStatus(){
  67. var tabs = this.getTabs();
  68. // console.log(tabs);
  69. var _this = this;
  70. // 显示当前选中的tab对应的pane组件,隐藏没有选中的
  71. tabs.forEach(function(tab){
  72. // console.log(tab.name === _this.currentValue);
  73. return tab.show = tab.name === _this.currentValue;
  74. })
  75. },
              // 点击tab标题时触发
  76. handleChange:function(index){
  77. var nav = this.navList[index];
  78. var name = nav.name;
  79. console.log(name);
                // 改变当前选中的tab,并触发下面的watch
  80. this.currentValue = name;
  81. }
  82. },
  83. watch:{
  84. value:function(val){
  85. this.currentValue = val;
  86. },
  87. currentValue:function(){
  88. this.updateStatus();
  89. }
  90. }
  91. })

getTabs是一个公用的方法,使用this.$children来拿到所有的pane组件实例。在遍历了每一个pane组件后,把他的label和name提取出来,构建成一个Object并添加到数据navList数组里。拿到navList后,就可以用v-for把tab的标题渲染出来,并且判断每个tab当前的状态。

通过以上代码,大家可以看到,在使用v-for指令循环显示tab标题时,使用v-bind:class指向了一个名为tabCls的methods来动态设置class名称。

点击每个tab标题时,会触发handleChange方法来改变当前选中的tab的索引,也就是pane组件的name。在watch选项里,我们监听了currentValue,当其发生变化时,触发了updateStatus方法来更新了pane组件的显示状态。

后记

总结一下:该选项卡组件,使用了组件嵌套的方式,将一系列pane组件作为tabs组件的slot;tabs组件和pane组件通信上,使用了$parent 和 $children 的方法访问父链和子链;定义了prop:value  和 data: currentValue。

以上就是Vue标签页组件的所有实现过程。

看JQ时代过来的前端,如何转换思路用Vue打造选项卡组件的更多相关文章

  1. 基于 Laravel 开发 ThinkSNS+ 中前端的抉择(webpack/Vue)踩坑日记【ThinkSNS+研发日记系列】

    在上一篇文章< ThinkSNS+基于Laravel master分支,从1到 0,再到0.1>,简单的介绍了 社群系统ThinkSNS+ ,这里分享在开发过程中,前端选择的心理活动. L ...

  2. 基于 Laravel 开发 ThinkSNS+ 中前端的抉择(webpack/Vue)踩坑日记

    在上一篇文章< ThinkSNS+基于Laravel master分支,从1到 0,再到0.1>,简单的介绍了 ThinkSNS+ ,这里分享在开发过程中,前端选择的心理活动. Larav ...

  3. web前端开发面试题(Vue.js)

    1.active-class是哪个组件的属性?嵌套路由怎么定义? 答:vue-router模块的router-link组件. 2.怎么定义vue-router的动态路由?怎么获取传过来的动态参数?  ...

  4. 前端工程化(三)---Vue的开发模式

    从0开始,构建前后端分离应用 导航 前端工程化(一)---工程基础目录搭建 前端工程化(二)---webpack配置 前端工程化(三)---Vue的开发模式 前端工程化(四)---helloWord ...

  5. vue仓库、组件间通信、前后台数据交互、前端储存数据大汇总

    目录 路由重定向 仓库介绍 vuex插件:可以完成任意组件间信息交互(移动端) 前端存储数据大汇总 前后台交互方式(重点) 前后台数据交互 axios插件:完成前后台ajax交互的 同源策略 - 前后 ...

  6. 在Vue前端项目中,附件展示的自定义组件开发

    在Vue前端界面中,自定义组件很重要,也很方便,我们一般是把一些通用的界面模块进行拆分,创建自己的自定义组件,这样操作可以大大降低页面的代码量,以及提高功能模块的开发效率,本篇随笔继续介绍在Vue&a ...

  7. 从互联网进化的角度看AI+时代的巨头竞争

    今天几乎所有的互联网公司在谈论和布局人工智能,收购相关企业.人工智能和AI+成为当今科技领域最灸手可热的名词,关于什么是AI+,其概念就是用以表达将"人工智能"作为当前行业科技化发 ...

  8. HTML5时代的纯前端上传图片预览及严格图片格式验证函数(转载)

    原文地址:http://www.2cto.com/kf/201401/274752.html 一.要解决什么样的问题? 在写这个函数之前,有们童鞋在群里问如何纯前端严格验证图片格式.这在html5时代 ...

  9. 一文带你看懂cookie,面试前端不用愁

    本文由云+社区发表 在前端面试中,有一个必问的问题:请你谈谈cookie和localStorage有什么区别啊? localStorage是H5中的一种浏览器本地存储方式,而实际上,cookie本身并 ...

随机推荐

  1. iOS 6.0中UIViewController被弃用的一些方法

    郝萌主倾心贡献.尊重作者的劳动成果,请勿转载. 假设文章对您有所帮助,欢迎给作者捐赠,支持郝萌主,捐赠数额任意.重在心意^_^ 我要捐赠: 点击捐赠 Cocos2d-X源代码下载:点我传送 概念:de ...

  2. 一个Web前端自学者的自述

    想来想去还是写下这篇文章,先说明,我精通JAVA编程语言和web前端常见的技术,个人是做JAVA的多,但是更加喜欢前端.因为我从高一开始接触JAVA,家父是黑马的JAVA讲师,自己对编程很热爱,在大学 ...

  3. List集合在遍历过程中的删除

    List集合在遍历过程中的删除:[1,1,2,3,4,5] for循环正续会漏掉一个1 for(int i=0;i<list.size();i++){ if(list.get(i).equals ...

  4. spring boot利用swagger和spring doc生成在线和离线文档

    参考博客地址: 在线文档:http://blog.didispace.com/springbootswagger2/ 离线文档:http://www.jianshu.com/p/af7a6f29bf4 ...

  5. laravel基本信息

    1.Bundle是Laravel的扩展包组织形式或称呼.Laravel的扩展包仓库已经相当成熟了,可以很容易的帮你把扩展包(bundle)安装到你的应用中.你可以选择下载一个扩展包(bundle)然后 ...

  6. UITableView的性能优化

    UITableView作为ios中使用最频繁的控件之一,其性能优化也是常常要面对的,尤其是当数据量偏大并且设备性能不足时.本文旨在总结tableview的几个性能优化tips,并且随着认识的深入,本文 ...

  7. APP的线程安全

    一般来说iOS中两个就够了,但是安卓中的第三个,iOS也是要注意的: 第一:网络方面,别人以为做数据请求用post会比get请求安全,但是这是错的,post请求虽然看起来你的请求是在请求体上,不像ge ...

  8. Java面试题汇总

    第一阶段:三年我认为三年对于程序员来说是第一个门槛,这个阶段将会淘汰掉一批不适合写代码的人.这一阶段,我们走出校园,迈入社会,成为一名程序员,正式从书本 上的内容迈向真正的企业级开发.我们知道如何团队 ...

  9. canvas学习api

    1.canvas.getContext():获取渲染上下文和绘画功能: 一.绘制矩形 2.ctx.fillRect(x,y,width,height):绘制矩形: 3.ctx.strokeRect(x ...

  10. HTML5 给图形绘制阴影(绘制五角星示例)

    几个属性 shadowOffsetX:阴影的横向位移量. shadowOffsetY:阴影的纵向位移量. shadowColor:阴影的颜色. shadowBlur:阴影的模糊范围. 属性说明 sha ...