问题
封装一个自定义组件,实现的这种嵌套子组件的写法。
<tab-container v-model="activeTab"
@tab-click="tabClick"
@tab-remove="tabRemove">
<tab-panel label="Tab-1">
// Tab-1
</tab-panel >
<tab-panel label="Tab-2">
// Tab-2
</tab-panel >
</tab-container>
数据更新是通过$children直接读取子组件数据进行更新
//TabContainer.vue
//template...
<div class="tab-items">
<div class="tab-item" ref="tabBar"
:class="{ active : activeTab === item.value}"
v-for="( item, index ) in tabs"
:key="item.value"
@click="clickHandler(item, $event)">
<div class="info">
<span class="info-label">{{item.label}}</span>
<span v-if="item.simpleTab === undefined"
class="info-id">
# {{item.value}}
</span>
</div>
<div v-if="item.closable !== undefined && tabs.length > 1" class="close"
@click.stop="removeHandler(item, index)">
<i class="el-icon-close"></i>
</div>
</div>
</div>
//...
//js...
mounted (){
this.children = this.$children
},
updated(){
this.children = this.$children
},
computed: {
tabs (){
return this.children.map(item => ({ label: item.label, value: item.value }))
},
...
}
...
但这里如果换成将tab-item进行for循环的写法,
会出现更新异常的情况 (距离我写此文的时间太久远,忘了具体是啥报错...)
<tab-container v-model="activeTab"
@tab-click="tabClick"
@tab-remove="tabRemove">
<tab-panel v-for="item in tabs"
:key="item.id"
:label="item.label"
:value="item.value">
...
</tab-panel>
</tab-container>
解决
经过一番谷歌并没找着什么想要的,但最后在element-ui源码中找到了类似的实现方式
结论:只需要将$children替换成$slots来实现同样效果即可。
**原因是$children并不能够访问足够多的有效数据来跟踪组件更新,
导致v-for循环操作时会产生子组件乱序。但使用$slots能有效解决这个问题**
在Github Vue仓库的issues #6702中有相同问题的讨论
最后贴一下改动后的代码
//TabContainer.vue
<template>
<div class="tabs-container">
<div class="tabs">
<div class="tab-items">
<div class="tab-item" ref="tabBar"
:class="{ active : activeTab === item.value}"
v-for="( item, index ) in tabs"
:key="item.value"
@click="clickHandler(item, $event)">
<div class="info">
<span class="info-label">{{item.label}}</span>
<span v-if="item.simpleTab === undefined"
class="info-id">
# {{item.value}}
</span>
</div>
<div v-if="item.closable !== undefined && tabs.length > 1" class="close"
@click.stop="removeHandler(item, index)">
<i class="el-icon-close"></i>
</div>
</div>
</div>
<div class="tab-scroll tab-scroll-prev"
v-if="scrollable">
<i class="el-icon-arrow-left"></i>
</div>
<div class="tab-scroll tab-scroll-next"
v-if="scrollable">
<i class="el-icon-arrow-right"></i>
</div>
</div>
<div class="content">
<slot />
</div>
</div>
</template>
<script>
export default {
name: 'TabContainer',
props:{
value: Number
},
data(){
return{
panes: [],
scrollable: false
}
},
computed:{
tabs (){
return this.panes.map(item => ({
label: item.label,
value: item.value,
closable: item.closable,
simpleTab: item.simpleTab
}))
},
activeTab: {
get(){
return this.value
},
set(val){
return this.$emit('input', val)
}
}
},
methods:{
// 修改部分
calcPaneInstances(){
let _this = this
var isForceUpdate = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
if(this.$slots.default){
let paneSlots = this.$slots.default.filter(vnode => {
return vnode.componentOptions && vnode.componentOptions.Ctor.options.name === 'TabPanel'
})
let panes = paneSlots.map(ref => {
return ref.componentOptions.propsData
})
var panesChanged = !(panes.length === this.panes.length && panes.every(function (pane, index) {
return pane === _this.panes[index];
}));
if (isForceUpdate || panesChanged) {
this.panes = panes;
}
}else if(this.panes.length !== 0){
this.panes = []
}
},
clickHandler(tab, e){
this.activeTab = tab.value
this.$emit('tab-click', tab, e)
},
removeHandler(tab, index){
let currentTabFlg = this.panes[index].value === tab.value
this.panes.splice( this.panes.findIndex( item => item.value === tab.value ), 1)
if(currentTabFlg){
index = index > 0 ? index - 1 : 0
this.activeTab = this.panes[index].value
}
this.$emit('tab-remove', tab.value, index)
}
},
created(){
this.calcPaneInstances.bind(null, true)
},
mounted(){
this.calcPaneInstances()
},
updated(){
this.calcPaneInstances()
},
}
</script>
//TabPanel.vue
<template>
<div class="tab-panel"
v-if="$parent.activeTab === value">
<slot />
</div>
</template>
<script>
export default {
name: 'TabPanel',
props:{
label: String,
value: Number,
closable: Boolean,
simpleTab: Boolean
}
}
</script>
vvnwxvjtpz
兄弟写的非常好 https://www.cscnn.com/
xicyawruys
想想你的文章写的特别好www.jiwenlaw.com
vkrxopvynv
不错不错,我喜欢看 https://www.ea55.com/
dwihazwixt
想想你的文章写的特别好https://www.237fa.com/
xcftnuaucx
看的我热血沸腾啊
bkwwsmifol
博主真是太厉害了!!!