需求
现在想要实现一个这样的效果:
开发者的开发姿势希望是这样的:
<template>
<div class="tab-demo">
<i-tabs :default-active-id="curTab" @on-change="handleChange">
<i-tab-panel tab="菜单1" id="1">content1 of tab pane</i-tab-panel>
<i-tab-panel tab="菜单2" id="2">content2 of tab pane</i-tab-panel>
<i-tab-panel tab="菜单3" id="3">content3 of tab pane</i-tab-panel>
<span slot="extra" class="extrabtn" @click="jumpToHome()">主页</span>
</i-tabs>
</div>
</template>
<script>
export default {
data() {
return {
curTab: '3'
};
},
methods: {
handleChange(curtab, prevtab) {
this.curTab = curtab.id;
},
jumpToHome() {
// xxxx
}
}
};
</script>
即组件api为:
i-tabs:
- default-active-id: 指定当前高亮的tab
- on-change:接受用户的点击切换事件,通过this.curTab=item.id手动实现更新
i-tab-panel:
- tab:当前tab的展示名字
- id:和default-active-id对应,决定高亮的命中
slot:extra:
- 在tab栏右侧的自定义渲染内容
设计
一步一步解决问题:
0、搭架子,将组件注册为全局组件
1、根据tab-panel渲染出静态的tab栏,名字、extra自定义都各归其位,id与default-active-id匹配高亮当前tab
=> tabs组件 通过this.$children可以拿到tab-panel组件实例(tab、id),维护一个navList渲染出tabBar
tabBar里高亮的元素就是activeId === item.id 实现的
2、响应用户的点击,高亮用户点击的tab
3、渲染当前高亮的tab内容(activeId所在的tabpanel自定义的内容 contentxxx of tab pane)
内容页的三个tabpane ,作为tabs里的children定义,在tabs组件内部使用slots接住的
1、tab-panel怎么渲染,渲染在tab组件的哪里
2、如何渲染?是全部一次性加载默认display:none 高亮时show高亮的tab,还是”异步加载”每次替换content的内容
==> 2.1 每次只展现一个tabpane。怎么实现其他的不见的?
way1、css的display控制 只有当前的才可见
这样的话实际上每次都是同步更改
一次性还是加载了 那么多的dom
way2、tabpane里控制是否渲染内容
决定当前tabpane是否渲染,不是由于tabs控制。是由tabpane来控制
tab-panel: 读取tabs上的activekey。如果与当前匹配就渲染(v-if)
===> 2.1.1 数据通信那块怎么搞, 同步activekey的更新
扩展需求
1、支持非受控组件使用模式 v-model
梳理依稀activekey(v-model) 和 defaultkey:
不管组件使用者是否传入v-model。
组件内部都要维护activekey变量
当用户传入的时候 只是会用activekey向外更新v-model的值
当有v-model特意指定的时候:
此时判断v-model的值是否与tabpane的key匹配,如果匹配那就默认高亮那一项 (同时更新内部的
如果没有匹配,那就看用户是否传入了defaultkey。
2、tabBar的菜单支持tab-panel传入插槽
2、tab支持滚动
当tabs宽度过小的时候 tabs会出现滚动
3、下面高亮线动画友好
现在的content是来自于 根据当前activeid是否与paneid匹配 由于pane组件决定是否渲染
所以如果都渲染的话会出现
div.tabs
div.tabs-tabpane
div.tabs-tabpane
div.tabs-tabpane 即使是inline-block 也会出现 闪动的现象
这样是没有效果的
<transition name="fade">
<div class="zhidao-tabs-tabpane" v-if="canrender">
<slot></slot>
</div>
</transition>
测试
总结一个最佳实践出来就好写很多了
其实没有我想的那么复杂
要测试的点:
1、当tabbar传入插槽
2、实践1非受控组件
3、实践2 受控组件
tabbar传入插槽
<tab-pane tab="关注" id="1">
<span slot="tab">
<em>Tab bar1</em>
</span>
tab1
</tab-pane>
<tab-pane tab="粉丝" id="2">
tab2
</tab-pane>
当slot=”tab”时 tabbar的循环渲染里不是 输出 pane.name 而是考虑这个pane的插槽
<div class="zhidao-tabs-nav">
<!-- div上面绑定样式宽度、color等 -->
<div :class="tabCls(item)" v-for="(item, index) in navList" @click="handleClick(index)">
<template v-if="item.isSlot">
# 怎么在这里得到item的插槽
<!-- <slot slot="tab">1</slot> -->
</template>
<template v-else-if="item.tab">{{item.tab}}</template>
</div>
</div>
<script>
// this.getTabs 得到tabs下面的tabpane的实例
this.getTabs().forEach((pane, index) => {
this.navList.push({
tab: pane.tab || index, // 用户没有传入name 则绑定index
isSlot: pane.$slots.tab ? 1 : 0,
id: pane.id
});
if (index === 0 && this.activeId === 'default') {
this.activeId = pane.id;
};
});
</script>
现在的问题是要把子的插槽 放在父里渲染
非受控实践
成功
受控实践
受控手动控制高亮失败
总结
拆分思路、思路的难点
实现过程中用到的vue特性
多方比较体会到的组件设计优良之处,以及随着一个功能的新增原始设计的优良带来的扩展性友好问题