背景介绍
在使用Vue2进行开发过程我们有时会碰到以下两种情况:
- 修改了模型里数组的数据,视图层没进行更新
- 向模型层的数据添加了新的属性,视图层没进行更新破题
解决问题
下面我们在 vue 2.6.11 处理进行测试
数组的处理
数组修改元素
这个场景是修改数组的元素,有以下一个组件:
<template>
<div>
<ul>
<li v-for="(item,i) in list" :key="item.value" class="item">
{{item.name}}
<button @click="handleClick(i)">修改当前名称</button>
</li>
</ul>
</div>
</template>
<script>
export default {
name: "Array",
data(){
return {
list: [
{name: "John",value: 1},
{name: "Lili",value: 2},
{name: "White",value: 3},
]
}
},
methods:{
handleClick(i){
this.list[i] = {
name: this.list[i].name + Math.floor(100 * Math.random()),
value: Math.floor(100 * Math.random())
};
}
}
}
</script>
<style lang="css" scoped>
.item {
text-align: left;
margin: 8px auto;
}
</style>
结果如下:
解决方案:
handleClick(i){
const currentItem = {
name: this.list[i].name + Math.floor(100 * Math.random()),
value: Math.floor(100 * Math.random())
};
// 方法一
Vue.set(this.list,i,currentItem);
// 方法二
this.$set(this.list,i,currentItem);
// 方法三
this.list.splice(i,1,currentItem);
}
以上共有三种解决方法。
数组新增元素
在某个指定的序号添加元素
方法一
<template>
<div>
<ul>
<li v-for="item in list" :key="item.value" class="item">
{{item.name}}
</li>
</ul>
<button @click="handleAdd">在第一项后新增一个元素</button>
</div>
</template>
<script>
export default {
name: "Array",
data(){
return {
list: [
{name: "John",value: 1},
{name: "Lili",value: 2},
{name: "White",value: 3},
]
}
},
methods:{
handleAdd(){
const addItem = {
name: "Burden",
value: 9
};
const addIndex = 1;
this.list.length = this.list.length + 1;
for(let j = this.list.length - 1;j>=addIndex;j--){
this.$set(this.list,j,this.list[j-1]);
}
this.list[addIndex] = addItem;
}
},
}
</script>
<style lang="css" scoped>
.item {
text-align: left;
margin: 8px auto;
}
</style>
方法二
将 方法一 里面 methods 里面的 handleAdd 修改为下面的即可:
handleAdd(){
const addItem = {
name: "Burden",
value: 9
};
const addIndex = 1;
this.list.splice(addIndex,0,addItem);
}
对象新增新的属性
<template>
<div class="hello">
<div v-if="obj.flag">
yes
</div>
<div v-else>
{{obj.data}}
</div>
<button @click="handleClick">切换flag</button>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
props: {
msg: String
},
data(){
return {
obj: {
flag: true
}
}
},
methods:{
handleClick(){
this.$set(this.obj,'data','flag 为false的数据');
this.obj.flag = !this.obj.flag;
}
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
</style>
源码剖析
自己download vue 2.6.11 的源码,
Vue.set 方法文件 src\core\global-api\index.js ,44-46行见下面
其中 set 引用自文件 src\core\observer\index.js 。
this.$set 方法在文件 src\core\instance\state.js,见下图
其中 set 同样引用自文件 src\core\observer\index.js 。
可以看出 Vue.set 与 this.$set 引用了同样的函数,下面来看看这个引用的函数。
这个函数在 src\core\observer\index.js 文件中。文件196-213行如下
/**
* Set a property on an object. Adds the new property and
* triggers change notification if the property doesn't
* already exist.
*/
export function set (target: Array<any> | Object, key: any, val: any): any {
if (process.env.NODE_ENV !== 'production' &&
(isUndef(target) || isPrimitive(target))
) {
warn(`Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`)
}
if (Array.isArray(target) && isValidArrayIndex(key)) {
target.length = Math.max(target.length, key)
target.splice(key, 1, val)
return val
}
if (key in target && !(key in Object.prototype)) {
target[key] = val
return val
}
const ob = (target: any).__ob__
if (target._isVue || (ob && ob.vmCount)) {
process.env.NODE_ENV !== 'production' && warn(
'Avoid adding reactive properties to a Vue instance or its root $data ' +
'at runtime - declare it upfront in the data option.'
)
return val
}
if (!ob) {
target[key] = val
return val
}
defineReactive(ob.value, key, val)
ob.dep.notify()
return val
}
总结:
- 数组和对象的修改使用 Vue.set 和 this.$set 的效果是一样的