「这是我参与2022首次更文挑战的第21天,活动详情查看:2022首次更文挑战」
先看下面场景
我们写一个todolist:
- list中有姓名、性别、年龄;
- 声明数据,数据中没有年龄;
- 做一个表单:选中一个姓名,然后给这个人添加加年龄;
效果图
代码结构
<template>
<div>
<div class="list">
<div v-for="(item, index) of list" :key="index">
姓名:{{item.name}},性别:{{item.sex}},年龄:{{item.age}}
</div>
</div>
<div class="operation">
<div>
姓名:
<select v-model="name">
<option v-for="(item, index) of list" :key="index" :value="item.name">
{{item.name}}
</option>
</select>
</div>
<div>
年龄:<input v-model="age" type="text" />
</div>
<div>
<button @click="add">添加</button>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'App',
data: () => {
return {
name: '',
age: '',
list: [
{name: '胡歌', sex: '男'},
{name: '刘亦菲', sex: '女'},
]
}
},
methods: {
add() {
console.log(this.list)
for (let item of this.list) {
if (item.name === this.name) {
item.age = this.age;
return;
}
}
console.log('未查询到')
}
}
}
</script>
遇到的问题
当选中‘刘亦菲’后,点击‘添加’按钮,页面没有反应???于是我们打印一下list:
- age数据已经添加进去了;
- list没有‘age’属性的set、get;
原因分析
vue在data中的数据进行劫持,是在创建阶段。之后通过item.age = this.age再添加数据时。不再添加劫持也不会触发组件刷新。
解决问题
这种场景我们可以使用$set来解决;
item.age = this.age;
替换成:
this.$set(item, 'age', this.age);
你会发现问题解决了。
$set源码解析
function set (target, key, val) {
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
}
var ob = (target).__ob__;
if (!ob) {
target[key] = val;
return val
}
defineReactive$$1(ob.value, key, val);
ob.dep.notify();
return val
}
上面代码做过一些简化。重点的部分是:
- (key in target && !(key in Object.prototype))已经存在key,更新。不存在则添加key。
- defineReactive$$1(ob.value, key, val)添加劫持。
- ob.dep.notify()通知组件更新。
Vue3.0
Vue3.0后就不需要$set了,因为Proxy是对整个对象进行劫持(仍需递归)。所有不需要考虑某个key。