效果
本节要实现的是导航标签切换功能:
html
<div class="tabs">
<ul>
<li class="is-active"><a>Pictures</a></li>
<li><a>Music</a></li>
<li><a>Videos</a></li>
<li><a>Documents</a></li>
</ul>
</div>
实现
首先来考虑标签如何实现,我们使用 name
属性让用户定义标签:
<tab name="图片"></tab>
<tab name="音乐"></tab>
<tab name="文档"></tab>
<tab name="视频"></tab>
定义具体的 tab
组件:
Vue.component('tab',{
template:`
<div></div>
`,
mounted(){
console.log(this);
}
});
我们打印出组件的对象,发现 name
的值并没有传递进来:
其实就是之前讲过的,组件的实例要传递数据给组件,必须在 props
中声明:
Vue.component('tab',{
props:['name'],
template:`
<div></div>
`,
mounted(){
console.log(this);
}
});
现在,组件对象里就可以看到 name
传递进来了。
接下来是 zen-tabs
:
Vue.component('zen-tabs',{
template:`
<div><slot></slot></div>
`
});
里面定义了一个 slot
,以便用于自定义 tab
,比如:
<zen-tabs>
<tab name="图片"></tab>
<tab name="音乐"></tab>
<tab name="文档"></tab>
<tab name="视频"></tab>
</zen-tabs>
现在的问题是,zen-tabs
组件如何获取 name
数据呢?我们不妨打印出来看看:
Vue.component('zen-tabs',{
template:`
<div><slot></slot></div>
`,
mounted(){
console.log(this);
}
});
效果如下:
也就是说,如果一个组件(zen-tabs
,称之为父组件)里面使用了另外一个组件(tab
,称之为子组件),那么可以通过 $children
获取子组件的数据。
Vue.component('zen-tabs',{
template:`
<div><slot></slot></div>
`,
mounted(){
this.tabs = this.$children;
},
data(){
return {
tabs:[]
}
}
});
现在,我们将子组件的数据赋值给了 tabs
变量了,然后就可以使用了:
Vue.component('zen-tabs',{
template:`
<div>
<div class="tabs">
<ul>
<li v-for="tab in tabs"><a href="#">{{tab.name}}</a></li>
</ul>
</div>
<div><slot></slot></div>
</div>
`,
mounted(){
this.tabs = this.$children;
},
data(){
return {
tabs:[]
}
}
});
效果如下:
接下来标签的激活功能。首先,我们为第一个标签添加激活功能看看:
<div id="root" class="container">
<zen-tabs>
<tab name="图片" selected="true"></tab>
<tab name="音乐"></tab>
<tab name="文档"></tab>
<tab name="视频"></tab>
</zen-tabs>
</div>
tab
组件中在 props
中定义 selected
,并赋予默认值:
Vue.component('tab',{
props: {
name:{require:true},
selected: {default:false}
},
template:`
<div></div>
`
});
最后,可以通过 selected
的值来决定是否添加激活类 is-active
:
Vue.component('zen-tabs',{
template:`
<div>
<div class="tabs">
<ul>
<li v-for="tab in tabs" :class="{'is-active':tab.selected === true}">
<a href="#">{{tab.name}}</a>
</li>
</ul>
</div>
<div><slot></slot></div>
</div>
`,
mounted(){
this.tabs = this.$children;
},
data(){
return {
tabs:[]
}
}
});
发现没效果:
这是因为,我们使用的的是 selected = "true"
,这种写法只能传递字面量,因此,传递的是字符串 "true"
,而我们使用了 ===
来判断传入的到底是不是布尔值 true
,结果就返回 false
了。
因此,如果要动态的传递属性,需要使用:
<tab name="图片" :selected="true"></tab>
这样话 "true"
就被当成表达式来解析了,就为布尔值 true
了。修改之后,效果就出来了:
接下来,就可以根据用户的点击来动态切换标签了:
component('zen-tabs',{
template:`
<div>
<div class="tabs">
<ul>
<li v-for="tab in tabs" :class="{'is-active':tab.selected === true}" @click="selectTab(tab)">
<a href="#">{{tab.name}}</a>
</li>
</ul>
</div>
<div><slot></slot></div>
</div>
`,
mounted(){
this.tabs = this.$children;
},
data(){
return {
tabs:[]
}
},
methods:{
selectTab(selectedTab){
this.tabs.forEach(function(tab){
tab.selected= (selectedTab.name == tab.name);
})
}
}
});
这样做,理论上是没问题的,实际上,会报错:
Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "selected"
为什么 Vue 不提倡这样做,因为我们在组件里面修改 selected
的值,这样可能会对外部造成影响,为了保持松耦合,请将 props
仅仅当成是一种传递数据(而非改变数据)的方式。我们可以自己在内部定义变量:
Vue.component('tab',{
props: {
name:{require:true},
selected: {default:false}
},
template:`
<div></div>
`,
mounted(){
this.isActive = this.selected;
},
data(){
return {
isActive:false
}
}
});
Vue.component('zen-tabs',{
template:`
<div>
<div class="tabs">
<ul>
<li v-for="tab in tabs" :class="{'is-active':tab.isActive=== true}" @click="selectTab(tab)">
<a href="#">{{tab.name}}</a>
</li>
</ul>
</div>
<div><slot></slot></div>
</div>
`,
mounted(){
this.tabs = this.$children;
},
data(){
return {
tabs:[]
}
},
methods:{
selectTab(selectedTab){
this.tabs.forEach(function(tab){
tab.isActive= (selectedTab.name == tab.name);
})
}
}
});
最后,优化一下该组件,首先是允许用户自定义视图:
<zen-tabs>
<tab name="图片" :selected="true">图片视图</tab>
<tab name="音乐">音乐视图</tab>
<tab name="文档">文档视图</tab>
<tab name="视频">视频视图</tab>
</zen-tabs>
只需要稍微修改下 tab
的模板:
Vue.component('tab',{
template:`
<div v-show="isActive">
<slot></slot>
</div>
`
最后是超链接功能,用计算属性来实现:
Vue.component('tab',{
computed:{
href(){
return '#' + this.name.toLowerCase().replace(/ /g,'-');
}
}
});
Vue.component('zen-tabs',{
template:`
<div>
<div class="tabs">
<ul>
<li v-for="tab in tabs" :class="{'is-active':tab.isActive=== true}" @click="selectTab(tab)">
<a :href="tab.href">{{tab.name}}</a>
</li>
</ul>
</div>
<div><slot></slot></div>
</div>
`,
通过计算属性,让超链接返回 #
+ 标签名
的方式,如果标签名中存在空格,就用 -
来代替。
附录:
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。