zoukankan      html  css  js  c++  java
  • 《Vue.js实战》章七 组件——标签页组件:思路详解

    先上带有部分注释的全部代码

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>Document</title>
        <script src="Vue.2.6.10.js"></script>
    </head>
    <body>
        <!-- 需求分析:
        1.每个标签页的主体内容(即html结构、文本、图片等)应当是由使用组件的父级控制的,这部分可以作为一个slot,
        slot的数量决定了标签切换按钮的数量。点击每个按钮时,另外的标签对应的slot应该被隐藏掉。第一种想法,在slot里写三个div,再
        根据需要隐藏和显示。更好的办法应该是让组件帮忙处理这部分逻辑,我们只需要聚焦slot内容本身。这种情况下,再定义一个子组件pane,
        嵌套在标签页组件tabs里,把业务代码放在pane的slot内,然后各个pane都作为整个tabs的组件。
        
            tabs和pane两个组件是分离的,但tabs上的标题应该由pane组件来定义,又因为slot写在pane里,所以应该在组件初始化(或标签标题动态改变)时,tabs从
            当前的pane里获取标题并保存起来。-->
        
        <div id="app" v-cloak>
            <tabs v-model="activeKey">
                <pane label="标签一" name="1">
                    标签一内容
                </pane>
                <pane label="标签二" name="2">
                    标签二内容
                </pane>
                <pane label="标签三" name="3">
                    标签三内容
                </pane>
            </tabs>
        </div>
    <script>
        Vue.component('pane',{
            name:"pane",
            template:'
                <div class="pane" v-show="show">
                    <slot></slot>
                </div>',
                data() {
                    return {
                        show:true//pane需要控制标签页内容的显示与隐藏,配合v-show
                        //点击到这个pane对应的标签页按钮时,这个pane的show值才设置为true
                        //既然这样的话就应该有唯一标识的值来标识这个pane,可以设置一个prop:name让用户来设置,但不是必必需的,默认从0开始
                        //这一步操作由pane执行,pane本身并不知道自己是第几个。
                        //还有prop:label,tabs组件需要将它显示在标签栏标题中
    
                    }
                },
                props:{
                    name:{
                        type:String
                    },
                    label:{
                        type:String,
                        default:''
                    }
                },//prop:label是用户可以动态调整的,所以在pane初始化、label更新时都需要通知父组件也更新,
                //可以直接通过this.$parent来访问tabs组件的实例来调用它的方法更新标题
                methods: {
                    updateNav(){
                        this.$parent.updateNav();//调用tabs的方法updateNav
                    }
                },
                watch: {
                    label(){
                        this.updateNav();
                    }
                },
                mounted() {
                    this.updateNav();//在pane初始化时调用一遍tabs的方法updateNav,同时监听prop:label,在其更新时也调用
                },
        })
    
    
        Vue.component('tabs',{
            template:'
                <div class="tabs">
                    <div class="tabs-bar">
                        <!--标签页的标题,需要使用v-for-->
                        <div 
                            :class="tabCls(item)"
                            v-for="(item,index) in navList"
                            @click="handleChange(index)">
                            {{ item.label }}
                        </div>
                    </div>
                    <div class="tabs-content">
                        <!--这里的slot即是嵌套的pane组件-->
                        <slot></slot>
                    </div>
                </div>',
                props:{
                    value:{
                        type:[String,Number]
                    }
                },
                data() {
                    return {
                        //由于不能修改value于是自己复制一份维护
                        currentValue:this.value,
                        //用于渲染tabs的标题
                        navList:[]
                    }
                },
                methods:{
                    tabCls:function(item){
                        return [
                            'tabs-tab',
                            {
                                //给当前选中的div加一个class
                                'tabs-tab-active':item.name === this.currentValue
                            }
                        ]
                    },
    
                    //点击tab标题时触发
                    handleChange:function(index){
                        var nav = this.navList[index];
                        var name = nav.name;
                        //改变当前选中的tab,并触发下面的watch
                        this.currentValue = name;
                        //更新value
                        this.$emit('input',name);
                        //触发一个自定义事件供父级使用
                        this.$emit('on-click',name);
                    },
                    getTabs(){
                        //通过遍历子组件得到所有panes组件
                        return this.$children.filter(function(item){
                            return item.$options.name === 'pane'
                        })
                    },
                    updateNav(){
                        this.navList = [];
                        //设置对this的引用,在function回调里this指向的并不是Vue实例
                        var _this = this;
    
                        this.getTabs().forEach(function(pane,index){
                            _this.navList.push({
                                label:pane.label,
                                name:pane.name || index
                            });
                            //如果没有给pane设置name就默认设置为索引
                            if(!pane.name){
                                pane.name = index;
                            }//设置当前选中的tabs的索引
                            if(index === 0){
                                if(!_this.currentValue){
                                    _this.currentValue = pane.name || index;
                                }
                            }
                        });
    
                        this.updateStatus();
                    },
    
                    updateStatus(){
                        var tabs = this.getTabs();
                        //再次遍历pane组件,不过这是为了将当前选中的
                        //tab对应的pane组件内容显示出来,并将没有选中的隐藏掉
                        //在上一步中可能需要我们来设置currentValue来标识当前选中项的name(只有在用户没有设置value时才会自动设置)
                        var _this = this;
                        //显示当前选中的tab对应的pane组件,隐藏没有选中的
                        tabs.forEach(function(tab){
                            return tab.show = tab.name === _this.currentValue;
                            // return tab.show = (tab.name === _this.currentValue);
                        })
                    }
                },
                
                watch:{
                    value:function(val){
                        this.currentValue = val;
                    },
                    currentValue:function(){
                        //在当前选中的tabs发生变化时更新pane显示状态
                        this.updateStatus();
                    }
                }
        });
        
        var app = new Vue({
            el:"#app",
            data:{
                activeKey:'1'
            }
        });
        
        
    </script>
    </body>
    </html>

    先来明确思路:

    务必明确插槽slot的作用与组件渲染方式。

     

    以下为pane组件(建议将每一点结合起来看)

    1.name:'pane',这里的name此时不是为了在组件内部递归调用自身,而是作为唯一标识,既可以专门设置一个prop:name,也可以不做设置在后面用index代替

    2.show:true,由pane配合v-show以及name控制标签页的显示与隐藏,注意这一步操作是由tabs组件来进行的。

    3.props的传入过滤,用户是可以动态调整prop:label的!因此如果在pane初始化或label更新时都需要通知父组件更新状态(即导航栏)

    4.子组件的updateNav实际上是调用了父组件的这个同名方法

    5.同4,watch与mounted中的updateNav方法也是调用父组件,作用域亦在父组件。

     

    以下为tabs组件,分析并不从上到下。

    6.先来看上面的updateNav()方法到底做了什么?

    先将tabs的子组件列表navList初始化,然后调用getTabs()方法,得到所有的pane组件,注意filter、$options。

    然后将组件的label值与name推入数组,如果我们没有给出name(如下图),那么就以索引值作为name。

    在遍历过程中,遍历到第一个组件时,才会触发if(index === 0){...},此时这里的index是作为参数传入的索引值(无论有无指定name),

    如果没有指定currentValue,那么此时将currentValue指定为这个pane的name或索引值。

    再结束前还会触发一次updateStatus方法,见下文。

    7.currentValue是什么??首先,我们知道这个变量存储着当前显示的标签页,上一点的操作实际上就是确保在初始化时有一个默认标签页显示。其他暂且不管。

    .

    8.先来看看模板中的这些是什么意思:

    首先得明确v-for在这里会生成navList中的各个pane组件,并为每一个div填充这些数据。

    tabCls的返回值是一个字符串数组,必定包含tabs-tab,如果这个标签页当前是被选中的,那么tabs的currentValue值即等于当前标签页的name,还会多获得一个active的类名。

    9.当点击这个标签时,触发handleChange函数,并传入当前这个标签的index值,在这个函数里会完成以下操作:

      1.先将当前标签以及name属性存储起来(nav,name)

      2.将tabs组件的currentValue修改为name值

      3.触发父组件的input事件与on-click自定义事件,

    我们都知道v-model的本质是一个语法糖,activeKey的值会被动态修改为name值。(activeKey存在于tabs的data中,这一修改是双向的,便于进行其他操作)。

    10.

    updateStatus()方法,实际上是负责控制标签页显示与隐藏的方法。

    再次遍历获取pane组件,并将结果再次遍历(注意,两次遍历分别的对象),如果tab.name === _this.currentValue,就可以露脸,否则隐藏。

    这一步中实际依据是否设置value有不同的处理结果,有可能只规定了value的类型而没有规定default,currentValue为null。

    注意!可能会都感到疑惑这个value的值在哪?

    事实上value值就是那个activeKey,还记得v-model这个语法糖吗?

    注意v-bind:value=“dataA”这一行!!!!

    紧接着由于没有指定value,currentValue会被自动设置(第六点),为1(因为本例中有给prop指定name,否则就是第一个索引值0)。

    如果指定了value值(本例value值即为activeKey),即指定要求初始化时被选中的标签页,那么pane初始化时,currentValue被指定为value值(以3为例),在tabs的updateNav()方法中,不会执行if(index === 0){...}中的改变,紧接着执行updateStatus(),name为3的pane组件会被显示出来。

     

    12.假定一个完整的执行流程:

    初始activeKey=2——v-model=2——value=2——cV=2——初始化,updateNav()——填充navList——执行updateStatus,将第二个组件显示出来。

    点击第一个标签页——触发handleChange,改变cV值,更新value——tabs中的watch监控到value与cV值变化,将cV值修改为value值,同时再次执行updateStatus,更改显示状态。

  • 相关阅读:
    poj 1753 -- Flip Game
    hdu 2209 -- 翻纸牌游戏
    文件系统的挂载与卸载挂载
    我的vim配置(一)
    Poj 3687 -- Labeling Balls
    主动激发“onclick”事件;prompt
    this
    函数嵌套
    调用函数时传递的实参个数arguments.length; ,函数定义时的形参个数sum.length
    回调函数,用户定义的排序规则
  • 原文地址:https://www.cnblogs.com/linbudu/p/11003734.html
Copyright © 2011-2022 走看看