需求
现在想要实现一个这样的效果:
开发者的开发姿势希望是这样的:
<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.tabsdiv.tabs-tabpanediv.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 则绑定indexisSlot: pane.$slots.tab ? 1 : 0,id: pane.id});if (index === 0 && this.activeId === 'default') {this.activeId = pane.id;};});</script>
现在的问题是要把子的插槽 放在父里渲染

非受控实践
成功
受控实践

受控手动控制高亮失败
总结
拆分思路、思路的难点
实现过程中用到的vue特性
多方比较体会到的组件设计优良之处,以及随着一个功能的新增原始设计的优良带来的扩展性友好问题
