「这是我参与11月更文挑战的第2天,活动详情查看:2021最后一次更文挑战」
hello 大家好,🙎🏻♀️🙋🏻♀️🙆🏻♀️
我是一个热爱知识传递,正在学习写作的作者,ClyingDeng 凳凳!
问题阐述
这个问题,遇到的真的是很奇怪的。
事情是这样的:这其实是我的一个项目需求。点击导航右侧更多按钮,出现对话框,可以拖拽导航列表,更换导航栏顺序。看着是不难的,但是当拖拽完成之后,你就会发现页面上的导航栏并未发生任何变化。
关于项目
App 父组件
<van-tabs
v-model="active"
swipe-threshold="4"
class="tab"
title-active-color="#3693FF"
color="#3693FF"
title-inactive-color="#4A4A4A"
@click-tab="chooseNav"
>
<van-tab v-for="item in navList" :key="item.name" :name="item.name" :title="item.title">
<component :is="item.component" class="tabPanel"></component>
</van-tab>
</van-tabs>
</div>
<sort-nav
:isSortNav="isSortNav"
:list="navListClone"
@newList="newList"
@noSortshow="noSortshow"
></sort-nav>
// 在点击右上角触发弹框子组件时,给子组件传入`isSortNav`(控制弹框显示)、`navListClone`(拖拽列表绑定数据)
子组件
<van-popup
v-model="isSortNav"
round
position="bottom"
:style="{ height: '70%' }"
:overlay-style="{ opacity: 0.5 }"
:close-on-click-overlay="false"
@click-overlay="noSortNav"
>
<div class="sortContent">
<div class="packUp">
<img src="@/assets/arrowDown.png" alt="" />
</div>
<div class="dataTittle">
<div>全部数据项</div>
<div class="rightTittle">长按右侧拖拽可排序</div>
</div>
<div class="dataOption">
<vue-draggable
:list="list"
class="list-group"
ghost-class="ghost"
@start="dragging = true"
@end="dragging = false"
>
<div v-for="item in list" :key="item.name" class="list-group-item">
<div class="list-item-left">
<div class="list-item-icon">
<img :src="require('@/assets/drag/' + item.name + '.png')" alt="" />
</div>
<div class="list-item-title">
{{ item.title }}
</div>
<div class="list-item-annotation">
{{ item.annotation }}
</div>
</div>
<div class="list-item-drag">
<van-icon name="wap-nav" />
</div>
</div>
</vue-draggable>
</div>
</div>
</van-popup>
// 点击退出弹框,给父组件传入最新排序的数据
noSortNav() {
this.$emit('noSortshow', !this.isSortNav)
this.$emit('newList', this.list)
},
解决方案
该项目基于vue2 + vant + vuedraggable。
排查
组件通信
首先确保自己组件之间的通信是否成功,通过打印或者断点方式,查看父子组件接收和传入的参数:
检查后发现并没有任何问题。
tab数据绑定
接下来,检查tab导航数据是否绑定成功。 在nav导航上添加数据,查看导航绑定的数据是否成功被拖拽所更改。如下图:
可以看出数据是发生了变化,但是导航tab并未发生变化。
那么说明我的组件和拖拽使用的没有问题。那么可能就是vant tabs导航组件渲染的问题。
解决
既然是组件没有刷新绑定新的数据。那么我们很快就会想到,现将绑定的数据置空,然后再去赋值。 emmm。。。
实践证明该方法不可行!
那么我们可以通过强制tabs组件刷新,来重新渲染更新后的组件数据。
组件强制渲染有很多种方法:
$forceUpdate
迫使 Vue 实例重新渲染。注意它仅仅影响实例本身和插入插槽内容的子组件,而不是所有子组件。
在上述情况中,使用$forceUpdate其实是无效的。因为重新渲染的是本身和插入插槽内容的子组件。而tabs组件是vant中已经封装好的,并不属于我们本身的插槽。
渲染失败!
改变key
我们看过vue内部的应该都知道,vue在重新渲染的是时候,是去对比它自身的key是否发生变化。那这样我们就会想到,给tabs组件绑定一个动态的key属性,在拖拽完成之后去改变这个key的值。
再试一下:
// van-tabs 组件绑定key为index,在父组件拿到子组件返回之后手动更新该组件的key值。
newList(val) {
this.navList = val
this.index++
},
ok,这个方法可行!
改变绑定数据
一开始赋值不行,那我等dom渲染完成之后呢?
既然想到要改变key,那么我们在拖拽完成之后,加一个nextTick是不是也可行呢?
在拖拽完成之后,置空绑定数据,再添加一个nextTick,在其内部去给绑定的数据重新赋值。
newList(val) {
this.navList = []
this.$nextTick(() => {
this.navList = val
console.log(this.active)
})
}
ok,这方法也可行!
tab循环绑定index为key
需要key的改变的话,那我是不是也可以通过索引去匹配标签页呢? 当数据发生变化
<van-tabs
v-model="active"
swipe-threshold="4"
class="tab"
title-active-color="#3693FF"
color="#3693FF"
title-inactive-color="#4A4A4A"
@click-tab="chooseNav"
>
<van-tab
v-for="(item, index) in navList"
:key="index"
:name="item.name"
:title="item.title"
>
<component :is="item.component" class="tabPanel"></component>
</van-tab>
</van-tabs>
<script>
newList(val) {
this.navList = val
},
</script>
ok,这方法也可行!
总结
van-tab组件对于json数组的导航数据,在内部,用唯一键标识(item.name)key值,其实在dom-diff新老节点作比较的时候,是默认其key属性是一样的。会进行头头、尾尾、头尾、尾头的比较,key值一样的时候会进行复用,导致导航栏不存在更新渲染。
vant中有这样一句话:在标签指定 name 属性的情况下,v-model 的值为当前标签的 name(此时无法通过索引值来匹配标签)。其实是比较建议绑定index的。