问题重现
这个bug是我在工作的时候处理过的一个问题,今天特地将bug问题剥离出来记录一下以作备忘。话不多说,直接上图:
问题排查
当时我看到之后第一反应以为下拉框启用了多选,去检查了一下后发现
el-select(v-model="options.selected.value")
el-option(v-for="item, index in options.list"
:key="index"
:label='item.name'
:value='item.value')
没开multiple啊
然后去检查了下逻辑代码,发现这个下拉选单的内容是在初始化时动态加载的,获取到内容后先赋值给下拉选单的列表,然后取列表第一个内容当作默认选中值。
加载代码段
...
mounted() {
this.getSelectList().then(resp => {
this.options.list = resp.data
this.options.selected = resp.data[0]
})
}
...
data代码段
data: () => ({
options: {
selected: {},
list: []
}
}),
这里我们用promise模拟一个数据请求
getSelectList() {
return new Promise((resolve, reject) => {
setTimeout(() => {
const resp = {
data: [
{ value: 1, name: '选项1' },
{ value: 2, name: '选项2' },
{ value: 3, name: '选项3' },
{ value: 4, name: '选项4' }
]
}
resolve(resp)
}, 1000)
})
}
检查了一下发现好像并没有什么不对,随后一起检查这个bug的同事把请求的内容直接放在本地data里测试了一下,bug消失了。那问题肯定就出现在在加载列表内容的那两行代码里。其实现在已经很明显了,我们直接祭出杀器Vue Devtools检查一下运行时的状态再确认一下。
当我们点击选项3的时候,发现不止是selected的值被改变了,list[0]值也被修改了!水落石出,这是一个由数据浅拷贝引发的bug。
由于selected在赋值时只是进行了浅拷贝,所以selected其实是list[0]的一个引用,所以list[0]的值也会被el-select修改成被选中的值,页面在渲染时发现list[0]的key和被选中的key相同,自然就显示成选中状态了。
问题解决
只需要把浅拷贝换成深拷贝即可,这里我们直接用JSON.parse()
和JSON.stringify()
深拷贝一下,这样selected和list[0]就不会相互干扰了。
mounted() {
this.getSelectList().then(resp => {
this.options.list = resp.data
this.options.selected = JSON.parse(JSON.stringify(resp.data[0]))
})
}
问题总结
- 在发现出现数据污染的情况,特别是数据里包含数组或对象(这个基本避免不了),或者从数组里取值赋给某个值的时候,要优先考虑深浅拷贝的问题。
- Vue Devtools真好用。