这个问题是在工作中发现的,其实也是个比较常见的问题,问题出现的情况是这样的:

带选择框的主要页面代码是这样的

1
2
3
4
5
6
//html
<el-select v-model="selectValue['id_' + current]" placeholder="请选择选项">
<el-option v-for="item in list" :key="item.id" :label="item.label" :value="item.id">
{{item.label}}
</el-option>
</el-select>
1
2
3
4
5
6
7
8
//vue-data
data: function() {
retun {
current: 0,
selectValue: {
}
}
}

可以看到这个select框是复用的,current改变的时候,改变select框中v-model的绑定值,因为这个current的数量和值是不确定的,所以办法在定义selectValue的时候在里面定义好出现的值,需要在从后台请求到相应的数据后再进行赋值,然后就发生了,修改current的值之后,select框里面的值无法选择,选择以后,select里面的选中值也不变。

出现这个问题的主要原因是,vue2中的双向绑定机制是不完美的,因为selectValue在初始化的时候,内部是没有值的,vue2中的双向绑定是在初始化的时候去劫持data中的每个值,为每个值添加一个dep类(用来发送消息),然后去收集页面上跟这个值有关的地方,为每个地方添加一个watcher类(用来接收消息)去订阅这个值的dep类。这样就可以在某个值变化的时候,通过dep类去给所有订阅这个dep类的watcher类发送消息,然后再调用watcher类中的更新函数,去更新页面视图。

这样就出现了一个问题,selectValue是一个对象,在他内部新增值的时候,新增的值是没有添加dep类的(因为是在初始化的时候添加的,初始化的时候这个新增的值还不存在),所以也不存在收集到的watcher类。所以这个值变化的时候,页面上是不会有显示的,但是这个值是已经相应的变化了,就是说这个新增的值不是双向绑定的。所以就会出现select框里面的值在页面上看起来是无法选择的(因为值变了页面不变)。

同类的问题很常见,在其他组件中将组件的值绑定到一个对象后期新增的属性上,也会发现页面和值不是实时绑定的。为了解决这个问题,vue2中提供了一个$set的函数,具体用法如下:

1
2
//就用上面的值举例子
this.$set(this.selectValue,'id_' + current, 'value')

可以看到,$set一共有三个参数,第一个是需要新增/改变值的对象,第二个是这个对象要新增/改变的值的key,第三个是这个对象要新增/改变的值的value。

它的基本逻辑就是首先会去判断第一个参数是不是vue实例data中的值;然后去判断第二个值的key是不是已经存在了,如果存在的话,就直接赋值改变,如果不存在的话,就劫持这个值的setter和getter,监听这个值并且收集相应的依赖(简单来说就是对这个值再进行一下初始化,这样的话,后续这个值变化的时候,页面就可以相应的变化)