背景:近期项目中,有小伙伴通过数组的forEach方法修改列表的属性值,视图并未更新。
之前小伙伴一直使用forEach进行同类操作,屡试不爽。
这次突然不灵了,小小的脑袋有大大的问号。
对于长期使用数组的map方法的我,并未遇到视图不更新的问题,我不李姐。
使用forEach和map方法,视图更新情况如何?为了彻底搞懂这个问题,下面用一个demo(一个列表的增删改)来验证视图更新情况。
一:Demo
代码准备
<!--template-->
<div v-for="item in list" :key="item.id">{{item}}</div>
<div class="active-btn" @click="btnClick">btn</div>
<div>{{textForHtml}}</div>
<script>
// data
list: []
textForData: '', // 只在data中定义
textForHtml: '', // data中定义 且应用到页面中
created () {
this.textForMethods = '' // 仅在methods 中使用
this.initDate()
}
// methods
initDate () {
// 通过定时器模拟ajax获取数据
setTimeout(() => {
const list = [
{id: 0, title: 'title0'},
{id: 1, title: 'title1'},
{id: 2, title: 'title2'},
{id: 3, title: 'title3'}
]
this.list = list
}, 200)
}
1、新增列表项
结论:
- 利用数组索引—失效
- 利用数组push方法—有效
- 利用数组unshift方法—有效
- 利用数组splice方法—有效
- 利用数组concat方法—有效
- 利用扩展运算符...—有效
- 利用Vue.set—有效
- 利用vm.$set—有效
代码运行结果截图:
方式 | 结果 |
索引 | |
push | |
unshift | |
splice | |
concat | |
扩展运算符... | |
Vue.set | |
vm.$set |
2、删除列表项
结论:
- 利用数组长度—失效
- 利用数组splice方法—有效
- 利用数组pop方法—有效
- 利用数组shift方法—有效
代码运行结果截图:
方式 | 结果 |
数组长度 | |
splice | |
pop | |
shift |
3、修改属性值—纯修改
结论:
- 利用直接索引—有效
- 利用for循环—有效
- 利用数组forEach—有效
- 利用map方法获取索引—有效
方式 | 结果 | |
直接 | ||
for forEach | ||
map |
4、新增属性—纯新增
-
4.1 利用索引—失效
方式 | 结果 | ||
直接 | |||
Object. assign | |||
Object. assign |
-
4.2 利用for循环获取索引—失效
方式 | 结果 | ||
直接 | |||
Object. assign | |||
Object. assign |
4.3 利用forEach/map获取索引,仅修改列表项—失效
方式 | 结果 | ||
直接 | |||
Object. assign | |||
Object. assign 修改item | |||
Object. assign 修改 下标值 | |||
... | |||
直接 | |||
Object. assign | |||
Object. assign 修改item | |||
Object. assign 修改 下标值 | |||
... |
-
4.4 利用map方法生成新数组—有效
方式 | 结果 | ||
直接 | |||
Object. assign | |||
Object. assign |
由上可知,
- 直接使用索引,间接获取索引(本例:for循环,forEach、map【仅修改列表项】方式),新增属性操作(形式不限),结果一致,均失效。
- map方法生成新数组方式,均生效。
既然直接使用索引,间接获取索引(本例:for循环,forEach、map【仅修改列表项】)结果一致,下面用forEach方法为代表探讨哪种方式可以使视图更新?
二:forEach方式探讨
序号 | 结果 | ||
1 | 新增列表项 + 修改一个非data中的值—失效 | ||
2 | 新增列表项 + 修改值【只在data中定义】—失效 | ||
3 | 新增列表项 + 修改值【data中定义 且应用到页面中】—生效 | ||
新增列表项 + 不修改值【data中定义 且渲染到页面中,原值不变】—失效 | |||
4 | 新增列表项 + 修改列表项—生效 | ||
新增列表项 + 不修改列表项【原值不变】—失效 | |||
5 | 新增多个列表项—失效 | ||
6 | 新增列表项【Vue.set】—生效 | ||
7 | 新增列表项【vm.$set】—生效 |
三:Vue2官网深入响应式原理一节 检测变化的注意事项
对于数组
Vue 不能检测以下数组的变动:
- 当你利用索引直接设置一个数组项时,例如:vm.items[indexOfItem] = newValue
解决方法:1)Vue.set:Vue.set(vm.items, indexOfItem, newValue)
2)Array.prototype.splice:vm.items.splice(indexOfItem, 1, newValue)
3)vm.$set:vm.$set(vm.items, indexOfItem, newValue)
- 当你修改数组的长度时,例如:vm.items.length = newLength
解决方法:Array.prototype.splice:vm.items.splice(newLength)
对于对象
Vue 无法检测 property 的添加或移除。由于 Vue 会在初始化实例时对 property 执行 getter/setter 转化,所以 property 必须在 data
对象上存在才能让 Vue 将它转换为响应式的。
对于已经创建的实例,Vue 不允许动态添加根级别的响应式 property。
解决方法:1)Vue.set:Vue.set(vm.someObject, 'b', 2)
2)vm.$set:this.$set(this.someObject,'b',2)
你可能需要为已有对象赋值多个新 property,比如使用 Object.assign() 或 _.extend()。但是,这样添加到对象上的新 property 不会触发更新。在这种情况下,你应该用原对象与要混合进去的对象的 property 一起创建一个新的对象。
// 代替 `Object.assign(this.someObject, { a: 1, b: 2 })`
this.someObject = Object.assign({}, this.someObject, { a: 1, b: 2 })
四:总结
最后,对于数组的增删改,结合Vue文档,和我们的代码实例,总结如下:
总之:
- 新增和删除数组项时,避免 1)直接利用索引 2)数组长度 的方式
- 修改数组项时,形式任意
- 数组项纯新增属性即有可能非响应的,所以,操作属性值建议的写法:
- 数组map方法遍历生成一个新数组,操作属性值形式任意
- 其他方法修改属性时【如:forEach】:
1)Vue.set
2)vm.$set
3)列表纯新增属性,且修改值【data中定义 且应用到页面中】
4)列表同时新增+修改属性 ,这四种方法真实有效。
forEach纯新增属性,不太行,避免这种写法,但:可以在ajax获取列表时,直接添加所有可能用到的属性。
The end.