1. 项目初始:
新建tabs.vue/tabs-body.vue/tabs-head.vue/tabs-item.vue/tabs-pane.vue
样式结构
tabs.vue
<template>
<div class="tabs">
<slot></slot>
</div>
</template>
<script>
export default {
name: 'GuluTabs',
props: {
selected: {
type: String
}
},
created(){
}
}
</script>
<style lang="scss" scoped>
.tabs{
}
</style>
app.js
new Vue({
el: '#app',
data: {
selectedTab: 'sports'
}
})
index.html
<div id="app">
<!--selected就是默认一开始选中的tab项,当你点击每个tab的时候需要更改selected的值,也就是更改selectedTab,这里需要使用@update:selected="selectedTab = $event",使用vue语法糖,可以写成下面的-->
<g-tabs :selected.sync="selectedTab">
<!--等价于<g-tabs :selected="selectedTab" @update:selected = "selectedTab = $event">-->
<g-tabs-head>
<g-tabs-item name="woman">
美女
</g-tabs-item>
<g-tabs-item name="finance">
财经
</g-tabs-item>
<g-tabs-item name="sports">
体育
</g-tabs-item>
</g-tabs-head>
<g-tabs-body>
<g-tabs-pane name="woman">
美女相关资讯
</g-tabs-pane>
<g-tabs-pane name="finance">
财经相关资讯
</g-tabs-pane>
<g-tabs-pane name="sports">
体育相关资讯
</g-tabs-pane>
</g-tabs-body>
</g-tabs>
</div>
相关补充:
$event
是指对应的事件信息。
对于原生元素(如 button、input)来说,$event
是原始的 DOM 事件。
对于自定义组件(如 child)来说,$event
是其自身$emit
里的第二个参数。
上面的$event
就是$emit
的第二个参数,也就相当于$emit('update:selceted', 'xxx')
,也就是update:selected事件触发时你写的第二个参数就比如我这里的'xxx',那么你的$event就是'xxx',其实就是子组件给父组件传递的那个数据就相当于
index.html
<g-tabs @update:selected="add">
</g-tabs>
<g-tabs @update:selected="selectedTab=$event">
{{selectedTab}}//这里的值就是$event也就是你下面触发update:selected事件传入的数据'xxx'
</g-tabs>
<script>
var app = new Vue({
data: {
selectedTab: ''
},
methods: {
//这里的value就是子组件传递来的数据,也就是$event,也就是当你在子组件触发父组件的事件的时候,$event就是你在子组件触发事件时传入的数据(也就是第二个参数)
add(value){
console.log(value)//xxx
}
}
})
</script>
子组件tabs.vue
<template>
<div class="tabs">
<slot></slot>
</div>
</template>
<script>
export default {
name: 'GuluTabs',
created(){
this.$emit('update:selected', 'xxx')
}
}
</script>
实现tab切换
通过事件中心发布订阅给所有的组件,谁变了就发布一个事件,其他的组件跟着订阅就好
在tabs.vue组件中声明一个事件中心
import Vue from 'vue'
export default {
name: 'GuluTabs',
data(){
return {
eventBus: new Vue()
}
},
provide(){
return {
eventBus: this.eventBus
}
},
created(){
console.log(this.eventBus)
//this.$emit('update:selected', $event.tagrget)
}
}
在data里声明这个eventBus,然后通过provide(https://cn.vuejs.org/v2/api/#provide-inject)方法将当前的eventBus传给eventBus好让其他的组件都可以使用,然后哦当前组件只需要通过this.eventBus就可以使用,这里通过proviede方法提供的属性,在其他任何子孙组件中可以直接通过inject['eventBus']注入,然后只要在其他组件中使用this.eventBus就可以拿到这个事件中心了,然后只需要在每个组件的事件中心里触发你的update:selcected事件,将它们的name就可以拿到它们对应的name了
在tabs-item和tabs-pane组件中分别监听事件中心的update:selected事件,当点击tabs-item的时候触发这个事件,并把当前name传给组件
tabs-item.vue
<template>
<div class="tabs-item" @click="xxx">
<slot></slot>
</div>
</template>
<script>
export default {
name: 'GuluItem',
props: {
disabled: {
type: Boolean,
default: false
},
name: {
type: String | Number,
required: true
}
},
inject: ['eventBus'],
created(){
this.eventBus.$on('update:selected',(name)=>{
console.log(name)
})
},
methods: {
xxx(){
this.eventBus.$emit('update:selected',this.name)
}
}
}
</script>
tabs-pane.vue
<template>
<div class="tabs-pane">
<slot></slot>
</div>
</template>
<script>
export default {
name: 'GuluPane',
inject: ['eventBus'],
created(){
this.eventBus.$on('update:selected',(name)=>{
console.log(name)
})
}
}
</script>
然后点击tabs-item就会打印出6个对应的name,上面三个item和下面三个pane的
- 问题1:我通过事件中心触发了update:selected事件,那会不会触发下面的yyy?
index.html
<g-tabs @update:selected="yyy">
</g-tabs>
<script>
var app = new Vue({
methods: {
yyy(value){
console.log(value)
}
}
})
</script>
答:不会。因为你的事件中心eventBus是在tabs.vue里的data里声明的一个new Vue(),而你上面的代码<g-tabs @update:selected="yyy">
是监听的<g-tabs>,也就是组件tabs,当tabs触发update:selected事件时,他才会执行yyy,要想触发tabs组件的update:selected事件,就要直接在tabs.vue里写
created(){
this.$emit('update:selected', '这是this $emit出来的数据')
}
然后yyy就会执行,就会打印出这是this $emit出来的数据
- 问题2:我如果在<g-tabs-head>里触发update:selected事件,那么<g-tabs>里的yyy会被触发吗?
index.html
<g-tabs @update:selected="yyy">
<g-tabs-head>
<g-tabs-head>
</g-tabs>
tabs-head
created(){
this.$emit('update:selected','这是tabs-head抛出的数据')
}
答:不会。原因vue的事件是不会冒泡的,你在哪个组件上触发事件,就要对应的在哪个组件对应的标签上监听事件。
切换功能实现
在tabs.vue里触发事件中心将当前的selected传给组件
<template>
<div class="tabs">
<slot></slot>
</div>
</template>
<script>
import Vue from 'vue'
export default {
name: 'GuluTabs',
props: {
selected: {
type: String,
required: true
},
direction: {
type: String,
default: 'horizontal',
validator(value){
return ['horizontal', 'vertical'].indexOf(value) >=0
}
}
},
data(){
return {
eventBus: new Vue()
}
},
provide(){
return {
eventBus: this.eventBus
}
},
mounted(){
//用来初始化的时候
this.eventBus.$emit('update:selected', this.selected)
}
}
</script>
然后在item和pane里监听事件中心,拿到事件触发时传入的selceted,如果当前的name等于传进来的selecetd就让active为true
tabs-item.vue
<template>
<div class="tabs-item" @click="xxx" :class="{active:active}">
<slot></slot>
</div>
</template>
<script>
export default {
name: 'GuluItem',
props: {
disabled: {
type: Boolean,
default: false
},
name: {
type: String | Number,
required: true
}
},
data(){
return {
active: false
}
},
inject: ['eventBus'],
created(){
this.eventBus.$on('update:selected',(name)=>{
if(this.name === name){
this.active = true
}else{
this.active = false
}
})
},
methods: {
//每次点击对应的tab将当前的name传给事件
xxx(){
this.eventBus.$emit('update:selected',this.name)
}
}
}
</script>
<style lang="scss" scoped>
.tabs-item {
flex-shrink: 0;
padding: 0 2em;
&.active{
background: red;
}
}
</style>
tabs-pane.vue
<template>
<div class="tabs-pane" v-show="active" :class="{active:active}">
<slot></slot>
</div>
</template>
<script>
export default {
name: 'GuluPane',
inject: ['eventBus'],
data(){
return {
active: false
}
},
props: {
name: {
type: String | Number,
required: true
}
},
created(){
this.eventBus.$on('update:selected',(name)=>{
if(this.name === name){
this.active = true
}else{
this.active = false
}
})
}
}
</script>
<style lang="scss" scoped>
.tabs-pane{
&.active{
background: red;
}
}
</style>