制作一个基于Vue的Tabs轮子

tabs轮子制作

1.先简单实现一下

1
<div id='app'></div>
1
.active{background:red}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var vm = new Vue({
el: '#app',
data() {
return {
selectedTab: 1
}
},
template: `
<div class="tabs">
<ol class="nav">
<li @click = 'selectedTab = 1' :class ='{ active: selectedTab === 1 }' >tab1</li>
<li @click = 'selectedTab = 2' :class ='{ active: selectedTab === 2 }' >tab2</li>
</ol>
<ol class="panels">
<li :class = '{active : selectedTab === 1}' >content1</li>
<li :class = '{ active: selectedTab === 2 }' >content2 </li>
</ol>
</div>
`
})

很简单,但是没有复用性。

2.轮子的实现

  • 思路:用户如何使用我这个组件呢?

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    var vm = new Vue({
    el: '#app',
    data() {
    return {
    selectedTab: 1
    }
    },
    template: `
    <tabs>
    <tabs-navs>
    <tabs-navs-item>1</tabs-navs-item>
    <tabs-navs-item>2</tabs-navs-item>
    </tabs-navs>
    <tabs-panels>
    <tabs-panels-item>content 1</tabs-panels-item>
    <tabs-panels-item>content 2</tabs-panels-item>
    </tabs-panels>
    </tabs>
    `
    })

    差不多这样调用,其他的不用管,一个tabs就出来了

  • 开始写,我先声明这些tabs的组件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    Vue.component('tabs',{})
    Vue.component('tans-navs',{})
    Vue.component('tabs-navs-item',{})
    Vue.component('tans-panels',{})
    Vue.component('tabs-panels-item',{})
    var vm = new Vue({
    el: '#app',
    data() {
    return {
    selectedTab: 1
    }
    },
    template: `
    <tabs>
    <tabs-navs>
    <tabs-navs-item>1</tabs-navs-item>
    <tabs-navs-item>2</tabs-navs-item>
    </tabs-navs>
    <tabs-panels>
    <tabs-panels-item>content 1</tabs-panels-item>
    <tabs-panels-item>content 2</tabs-panels-item>
    </tabs-panels>
    </tabs>
    `
    })

    现在有个问题,我怎么知道要选中那个tab呢?用1,2不大好,所以,改为字符串,并且给下面加上name

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    Vue.component('tabs',{
    props: ['selectedTab'],
    template: `
    <div class='tabs'>
    <slot/> //注意有子元素的话,添加 插槽
    </div>
    `
    })
    Vue.component('tans-navs',{
    template: `
    <div class='tabs-navs'>
    <slot/>
    </div>
    `
    })
    Vue.component('tabs-navs-item',{
    props: ['name'],
    template: `
    <div class='tabs-navs-item'>
    <slot/>
    </div>
    `
    })
    Vue.component('tans-panels',{
    template: `
    <div class='tabs-panels'>
    <slot/>
    </div>
    `
    })
    Vue.component('tabs-panels-item',{
    props: ['name'],
    template: `
    <div class='tabs-panels-item'>
    <slot/>
    </div>
    `
    })
    var vm = new Vue({
    el: '#app',
    data() {
    return {
    selectedTab: 'tab1'
    }
    },
    template: `
    <tabs :selectedTab = 'tab1'>
    <tabs-navs>
    <tabs-navs-item name = 'tab1' >1</tabs-navs-item>
    <tabs-navs-item name = 'tab2' >2</tabs-navs-item>
    </tabs-navs>
    <tabs-panels>
    <tabs-panels-item name = 'tab1'>content 1</tabs-panels-item>
    <tabs-panels-item name = 'tab2'>content 2</tabs-panels-item>
    </tabs-panels>
    </tabs>
    `
    })

    这样就初见雏形了

  • 下面就是重点了,孙子怎么知道爷爷的 selectedTab的值呢?

    答案是通过孙子的爸爸,爸爸获得selectedTab,然后再给孙子,

    问题又来了,爸爸怎么从爷爷那里获得呢?

    孙子,爸爸自己定义一个属性selectedTab,再写一个methods: SelectedTab(tab),爷爷遍历所有的爸爸,然后调用爸爸的SelectedTab(tab)把爷爷的selectedTab传给爸爸的selectedTab,同理,爸爸再用同样的方式传给孙子。

    P.S 爷爷:tabs , 爸爸 : tabs-navs , 孙子: tabs-navs-item

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    Vue.component('tabs', {
    props: ['selectedTab'],
    template: `
    <div class='tabs'>
    <slot/>
    </div>
    `,
    mounted() {
    this.$children.forEach((vm) => { //遍历所有的孩子
    if (vm.$options.name === 'tabs-navs') { //如果是爸爸
    console.log('爷爷的selectedTab是'+this.selectedTab)
    vm.SelectedTab(this.selectedTab) //通过爸爸的selectedTab方法,传给儿子
    }else if(vm.$options.name === 'tabs-panels'){
    vm.SelectedTab(this.selectedTab)
    }
    })
    }
    })
    Vue.component('tabs-navs', {
    data() {
    return {
    selectedTab: undefined //爸爸自己有一个selectedTab属性
    }
    },
    template: `
    <div class='tabs-navs'>
    <slot/>
    </div>
    `,
    methods: {
    SelectedTab(tab) {
    this.selectedTab = tab //通过这个方法,从爷爷那里获取selectedTab
    console.log('爸爸获取到了爷爷的selectedTab是'+this.selectedTab)
    this.$children.forEach((vm)=>{ //同样的方法传给孙子
    if(vm.$options.name === 'tabs-navs-item'){
    vm.SelectedTab(this.selectedTab)
    }
    })
    }
    }
    })
    Vue.component('tabs-navs-item', {
    props: ['name'],
    data () {
    return {
    selectedTab:undefined
    }
    },
    template: `
    <div class='tabs-navs-item' :class = '{active}'> //通过计算属性,判断active
    <slot/>
    </div>
    `,
    computed: {
    active() {
    return this.selectedTab === this.name //通过计算属性,判断active
    }
    },
    methods:{
    SelectedTab(tab){
    this.selectedTab = tab
    console.log('儿子获取到了从爸爸的selected是'+this.selectedTab)
    }
    }
    })
    Vue.component('tabs-panels', {
    data () {
    return {
    selectedTab:undefined
    }
    },
    template: `
    <div class='tabs-panels'>
    <slot/>
    </div>
    `,
    methods:{
    SelectedTab(tab){
    this.selectedTab = tab;
    this.$children.forEach((vm)=>{ //同样的方法传给孙子
    if(vm.$options.name === 'tabs-panels-item'){
    vm.SelectedTab(this.selectedTab)
    }
    })
    }
    }
    })
    Vue.component('tabs-panels-item', {
    props: ['name'],
    data () {
    return {
    selectedTab:undefined
    }
    },
    template: `
    <div class='tabs-panels-item' :class = '{active}'>
    <slot/>
    </div>
    `,
    computed:{
    active(){
    return this.selectedTab === this.name
    }
    },
    methods:{
    SelectedTab(tab){
    this.selectedTab = tab
    }
    }
    })
    var vm = new Vue({
    el: '#app',
    data() {
    return {
    selectedTab: 'tab1'
    }
    },
    template: `
    <tabs :selectedTab = 'tab1'>
    <tabs-navs>
    <tabs-navs-item name = 'tab1' >1</tabs-navs-item>
    <tabs-navs-item name = 'tab2' >2</tabs-navs-item>
    </tabs-navs>
    <tabs-panels>
    <tabs-panels-item name = 'tab1'>content 1</tabs-panels-item>
    <tabs-panels-item name = 'tab2'>content 2</tabs-panels-item>
    </tabs-panels>
    </tabs>
    `
    })

    这样,selectedTab 整个传递的线路,我们就打通了。tabs-panels的方法同理

  • 下面也是重点,我点击tabs-navs-item的时候,孙子要告诉爷爷该切换了,但孙子不能直接告诉爷爷,先告诉爸爸,爸爸再告诉爷爷。

    注意:Vue没有冒泡,孙子触发的$emit事件,爸爸是不知道的,所以要用爸爸监听$on孙子,爷爷监听$on爸爸,然后爷 爷再把数据传出去update()

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    //孙子
    onClick(){
    this.$emit('update:selectedTab', this.name)
    //孙子传出去。
    //name = 'tab1'的作用在这里体现,之前的vm.$options.name其实是 component 的 name
    }
    //爸爸
    mounted() {
    this.$children.forEach((vm) => {
    if (vm.$options.name === 'tabs-navs-item') {
    vm.$on('update:selectedTab', (e) => { //监听儿子的 update:selectedTab
    console.log('爸爸知道了你要更新selectedTab是' + e)
    this.$emit('update:selectedTab', e) //爸爸传给爷爷需要改的数
    })
    }
    })
    }
    //爷爷
    mounted() {
    this.$children.forEach((vm) => { //遍历所有的孩子
    if (vm.$options.name === 'tabs-navs') {
    console.log('爷爷的selectedTab是' + this.selectedTab)
    vm.SelectedTab(this.selectedTab) //通过儿子的selectedTab方法,传给儿子
    //爷爷监听爸爸的update,更新selectedTab属性,更新操作在updatde里面做
    vm.$on('update:selectedTab',(e)=>{
    console.log('爷爷知道了爸爸给我的selectedTab是' + e)
    this.$emit('update:selectedTab',e)
    })
    } else if (vm.$options.name === 'tabs-panels') {
    vm.SelectedTab(this.selectedTab)
    }
    })
    },
    updated() { //更新selectedTab
    this.$children.forEach((vm) => {
    if (vm.$options.name === 'tabs-navs') {
    vm.SelectedTab(this.selectedTab)
    } else if (vm.$options.name === 'tabs-panels') {
    vm.SelectedTab(this.selectedTab)
    }
    })
    }

    //然后体现在 template 里 ,使用 .sync 修饰符
    var vm = new Vue({
    el: '#app',
    data() {
    return {
    value: 'tab1'
    }
    },
    template: `
    <tabs :selectedTab.sync = 'value' > // 在这里更新
    <tabs-navs>
    <tabs-navs-item name = 'tab1' >1</tabs-navs-item>
    <tabs-navs-item name = 'tab2' >2</tabs-navs-item>
    </tabs-navs>
    <tabs-panels>
    <tabs-panels-item name = 'tab1'>content 1</tabs-panels-item>
    <tabs-panels-item name = 'tab2'>content 2</tabs-panels-item>
    </tabs-panels>
    </tabs>
    `
    })