在学习了vue的基础知识后,尝试着做了一个Tab选项切换并对其进行封装处理,下面我们就来开始逐步实现
涉及的知识:
- $slots:插槽
- $parent:边界管理->?儿子通过$parent读取父级的属性和方法
- props
- 自定义事件:$emit
在开始该案例之前,我们先来看看其视图结构,大致如下:
<tabs :currentIndex="index" @onIndex="changeHandle">
<tab index="1" label="导航一">内容1</tab>
<tab index="2" label="导航二">内容2</tab>
<tab index="3" label="导航三">内容3</tab>
</tabs>
而在浏览器里,会自动渲染成大致如下结构:
<div>
<div>
<ul>
<li>导航1</li>
<li>导航2</li>
<li>导航3</li>
</ul>
</div>
<div>
<div>内容1</div>
<div>内容2</div>
<div>内容3</div>
</div>
</div>
首先,通过 vue create xxx 命令,创建一个 vue 项目,并在 src/components 目录下新建一个名为 tabs 的文件夹,在该文件夹里分别新建 index.js、tabs.vue、tab.vue、content.vue,它们之间的关系如下:
- 入口文件:index.js
- 组件:tabs(tab的父组件)、tab(content的父组件)、content(tab的子组件)
组件之间的关系
tabs?->?tab:在我们封装组件中,他俩之间是通过slots进行处理的
tabs?->?content:引用关系
tab?->?content:在我们封装组件中,他俩之间是通过slots进行处理的
其次,在main.js 里引入:
import Tabs from "./components/tabs"
Vue.use(Tabs)
下面我们就直接上源码,关于相应的解释和说明都在源码里有详细的解释,因此在这里就不去过多的叙述
src/components/tab/tab.vue
<script>
export default {
name:"Tab",
/**
*如何才能读到 label里面的值?
* label 是 tab 里的一个属性,因此可以通过 props 的方式进行
*
*/
props: {
label:{
type:String,
default:"tab"
},
index:{
type:[String,Number],
default:1
}
},
computed: {
//控制是否高亮显示
/**
* 返回true或者false
* true--代表两者相等
* false--代表两者不相等
*/
isActive(){
return this.$parent.currentIndex == this.index;
}
},
mounted () {
// console.log(this.$parent.currentIndex);//1
//this.$slots.default代表的就是tab里面的插槽,也就是内容
// this.$parent.pans.push(this.$slots.default);
this.$parent.pans.push(this);//将tab自己本身传给了pans,那么pans里面包含的就是完整的两个tab
},
methods: {
/**
*改变 currentIndex的值就可以改变高亮
*但是此时问题又来了,事件是写在tab里面的,而currentIndex 是在App.vue里,
*但是我们知道 tab属于孙子,tabs属于父亲,App属于爷爷, 于是我们可以尝试先由孙子传到父亲,再由父亲传给爷爷即可
* 我们在tab里可以通过 this.index读取到鼠标点击时所对应的值,因此只需将其传递给tabs,再由tabs传递给App
*这里有两种解决的方法,一是利用子传父,一级一级向上传,但是相对来说比较麻烦;而是利用事件回调,我们选用事件回调
*
*/
clickItemHandle(){
//将index传递给tabs
this.$parent.getIndex(this.index);
}
},
render(){
// console.log(this.isActive);
let classNames = {
tab:true,
active:this.isActive
};
return (
<li onclick={ this.clickItemHandle } class={ classNames }>{ this.label }</li>
)
}
}
</script>
<style scoped>
.tab {
flex: 1;
list-style: none;
line-height: 40px;
margin-right: 30px;
position: relative;
text-align: center;
}
.tab.active {
border-bottom: 2px solid blue;
}
</style>
src/components/tab/tabs.vue
<script>
import Content from "./content.vue"
export default {
name:"Tabs",
data () {
return {
//存放所有内容部分的一个容器
pans:[],
}
},
props:{
currentIndex:{
type:[String,Number],
default:1
}
},
components: {
Content
},
methods:{
//读取tab传递过来的index
getIndex(index){
// console.log(index);
//子传父,传递给App即可
this.$emit("onIndex",index);
}
},
/**
* 在 ul 元素里,承载的是li,也就是导航1、导航2,而导航1、导航2实际上是在 tab 里,因此,ul里面嵌套的其实就是tab
* 我们刚刚在App.vue里引入的时候,tab是属于tabs的子元素,如果正常通过vue从tabs里面读取tab,此时读它的插槽即可,也就是通过 this.$slots.default 读到其内容
* this.$slots.default等价于li
*/
render(){
return (
<div>
<ul class="tabs-header">{this.$slots.default}</ul>
<Content pans={this.pans} />
</div>
)
}
}
</script>
<style scoped>
.tabs-header {
display: flex;
list-style: none;
margin: 0;
padding: 0;
border-bottom: 2px solid #ededed;
}
</style>
?
src/components/tab/content.vue
<script>
export default {
name:"Content",
props: {
pans:{
type:Array,
default:()=>{
return []
}
}
},
render(){
return (
<div>
{
this.pans.map((ele,index)=>{
// ele.$slots.default 读取 tab 里面的内容
// ele.isActive ? ele.$slots.default : "" 等价于添加和删除,在不停地切换的同时即在不停的添加和删除,这是比较消耗性能的
return <div style={{display:ele.isActive ? 'block' : 'none' }}>{ ele.$slots.default }</div>;//显示和隐藏
})
}
</div>
)
}
}
</script>
<style>
</style>
src/components/tab/index.js
import Tabs from "./tabs.vue"
import Tab from "./tab.vue"
export default (Vue) => {
Vue.component(Tabs.name, Tabs)
Vue.component(Tab.name, Tab)
}
App.vue
<template>
<div id="app">
<!--
这里该如何实现导航的高亮显示?
实现的方法就是:判断当前的currentIndex的值是否等于其对应的index的值,如果相等,那么对应的index所对应的内容就高亮显示
然后,我们可以分别在tab.vue和tabs.vue里通过props得到其对应的属性 current 和 index
但是此时问题就来了,current 和 index 根本就没在同一个组件里,那么该如何对两者进行对比?
由于tabs和tab属于子父级关系,所以我们可以可通过 $parent 在tab里越级直接读取tabs的元素
-->
<tabs :currentIndex="currentIndex" @onIndex="getIndexHandle">
<tab index="1" label="中国地图">
<div>内容1</div>
</tab>
<tab index="2" label="世界地图">
<div>内容2</div>
</tab>
</tabs>
</div>
</template>
<script>
export default {
name: 'App',
data () {
return {
currentIndex:1
}
},
components: {
},
methods: {
//接收tabs传递过来的index
getIndexHandle(index){
// console.log(index);
this.currentIndex = index
}
}
}
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
?
|