前言
在做项目的时候,发现了数组变化然后视图不发生变化的情况,附上代码
<van-tabs
title-active-color="#ffb001"
title-inactive-color="#838a96"
line-height=".06rem"
swipeable
lazy-render
@rendered="changeCate"
offset-top=".8rem"
>
<van-tab
v-for="item in cateList"
:key="item.id"
:title="item.type_name"
class="header"
>
<div v-if="item.list">
<van-list
v-model="load"
:finished="item.finished"
finished-text="没有更多了"
:error.sync="error"
error-text="请求失败,点击重新加载"
@load="getMoreGame(item.id, 1)"
>
<van-pull-refresh
v-model="load"
@refresh="getMoreGame(item.id, 2)"
success-text="刷新成功"
:disabled="item.finished"
>
</van-pull-refresh>
</van-list>
</div>
</van-tab>
</van-tabs>
逻辑结构如下
<script>
export default {
components: {
},
data() {
return {
cateList: [],
cateId: 0,
queryInfo: {
pageindex: 1,
pagesize: 50,
type_id: '',
key: ''
},
load: false,
error: false
}
},
created() {
this.getCateList()
},
methods: {
async getCateList(num) {
const res = await this.$http.get('/api/?ac=get_gametype')
this.cateList = res.data
this.cateList.unshift({
id: 0,
type_name: '全部'
})
},
async getCateGame(num) {
this.load = true
this.queryInfo.type_id = num
const res = await this.$http.get(
'/api/?ac=get_gamelist_by_typeid',
this.queryInfo
)
res.data.list.forEach(
item =>
(item.online_users = Math.ceil(item.online_users / 10000).toString())
)
Object.assign(this.cateList[num], res.data, {
queryInfo: Object.assign({}, this.queryInfo),
finished: false,
load: false
})
this.load = false
this.$forceUpdate()
},
changeCate(name, title) {
this.getCateGame(name)
}
}
</script>
然后呢就是在获取数组的时候初次渲染回调用rendered的回调changeCate,
在里面,然后会对刚开始的数组进行赋值,但是发现数组改变之后视图不进行更新会报错。后来查资料发现是因为vue不能检测一下数组的变动:
如用索引直接设置一个数组项
vm.a[index]=value;
或者修改数组的长度
vm.a.length=2;
这样的修改都不是响应式的。
Vue的响应式原理
当把js对象传给Vue的实例的data的时候,vue将遍历此对象的所有属性,把属性用object.defineproperty全部转化成为getter/setter也就是当属性改变的时候会调用setter函数进行更改,但setter和getter是有局限性的。
如下面的例子:
var person = {};
Object.defineProperties( person, {
age: {
defaultValue: 11,
get: function () {
return this.defaultValue;
},
set: function (val) {
this.defaultValue = val;
console.log("触发了set")
}
}
});
person.age=12;//这里会触发setter
//属性值变成数组的时候也会调用setter
person.arr=[1,2,3]
//但是下面这样不会调用setter
person.arr[0]=222
//同理设置对象的时候也一样,把某个属性设置为对象的时候会调用setter,但是设置这个对象的某个属性的时候不会触发setter 如下
person.aaa={bbb:1} //触发
person.aaa.bbb=2 //不触发
为了解决当你利用索引直接设置一个数组项问题,以下两种方式都可以实现和 vm.items[indexOfItem] = newValue 相同的效果,同时也将在响应式系统内触发状态更新
// Vue.set
Vue.set(vm.items, indexOfItem, newValue)
// Array.prototype.splice
vm.items.splice(indexOfItem, 1, newValue)
你也可以使用 vm.$set 实例方法,该方法是全局方法 Vue.set 的一个别名:
vm.$set(vm.items, indexOfItem, newValue)
为了解决当你修改数组的长度问题,你可以使用 splice:
vm.items.splice(newLength)
当修改对象的属性或为对象添加属性时,应该使用以下方法:
vm.$set(vm.userProfile, 'age', 27)
或者
Vue.set(vm.userProfile, 'age', 27)
有时你可能需要为已有对象赋值多个新属性,比如使用 Object.assign() 或 _.extend()。在这种情况下,你应该用两个对象的属性创建一个新的对象。所以,如果你想添加新的响应式属性,不要像这样:
Object.assign(vm.userProfile, {
age: 27,
favoriteColor: 'Vue Green'
})
你应该这样做:
vm.userProfile = Object.assign({}, vm.userProfile, {
age: 27,
favoriteColor: 'Vue Green'
})
由于数据响应原理机制, Vue 不允许动态添加根级响应式属性,所以你必须在初始化实例前声明所有可能用到的根级响应式属性,且为这些属性都设一个初值,哪怕只是一个空值。
回归正题,我项目中遇到的这个问题,解决方法:
-
运用this.$forceUpdate()强制刷新。
-
使用vm.$set(vm.items, indexOfItem, newValue)