需求:点击按钮,更新马冬梅信息
逐个属性修改
<div id="root">
<h2>{{title}}</h2>
<button @click="updateMei">更新马冬梅信息</button>
<ul>
<li v-for="(p,index) in persons" :key="p.id">
{{p.name}}-{{p.age}}-{{p.gender}}
</li>
</ul>
</div>
提示:点击按钮,添加绑定事情,修改 persons 信息
new Vue({
el: '#root',
data: {
title: '更新列表信息',
persons: [
{ id: "001", name: "马冬梅", age: 18, gender: "女" },
{ id: "002", name: "周冬雨", age: 19, gender: "女" },
{ id: "003", name: "周杰伦", age: 21, gender: "男" },
{ id: "004", name: "温兆伦", age: 22, gender: "男" },
],
},
methods: {
updateMei(){
this.persons[0].name = '马老师'
this.persons[0].age = 50
this.persons[0].gender = '男'
}
},
});
提示:通过索引,挨个修改属性值;
发现问题
提示:单个属性修改时没有问题;逐行属性修改时,会遇到问题;
methods: {
updateMei(){
this.persons[0]={id:'001',name:'马老师',age:50,gender:'男'}
}
},
看下效果:
这样点完后,“马冬梅”这行数据,却没有更新;
但是控制台输出没有问题:
Vue监测对象数据
Vue会对 data 数据进行监测,才能完成响应式;vm._data 里存着 data 的备份, _data 里属性的变化(如:title 变了),会引起 setter 的调用,重新解析模板,生成新页面;
提示:title 一改,setter 就调 ,重新解析模板,生成新的虚拟DOM,新旧虚拟DOM对比,更新页面;
模拟监测对象
根据提示,写一个模拟的过程:既然是模拟,就不用引入 vue.js 了;
let data ={
name:'jack',
age:30
}
// 创建一个监视的实例对象,用于监视 data 中的属性的变化
const obs = new Observer(data);
console.log(obs);
// 准备一个 vm 实例对象;
let vm = {}
vm._data = data = obs // 复制副本
// 创建监视构造函数
function Observer(obj){
// 汇总对象中所有的属性形成一个数组
const keys = Object.keys(obj);
keys.forEach((k)=>{
Object.defineProperty(this,k,{
get(){
return obj[k]
},
set(val){
console.log(`${k}被修改了,生成虚拟DOM,要去解析模板...`);
obj[k] = val
}
})
});
}
提示1:创建的构造函数,给实例对象绑上了 getter 和 setter ;通过 Object.defineProperty() 方法
提示2:实例化对象后,obs 的属性存取方式使用了 存取器属性,可以参考使用对象
提示3: vm._data = data = obs
这么一连等之后,各对象的属性就联动起来了,即数据代理;
看一下连等前的 data 和 obs :data 是数值描述,obs 是存取描述;
看一下连等之前 data 和 obs 的属性描述符:
连等后的vm._data 、data 和 obs 就完全一样:
data 的数据变化,obs 也跟着变化,同样的, obs 数据变化, data 也会变化 ;
这里只模拟了单级属性的“联动”,多层级的属性“联动”,没有实现;
这只是模拟 Vue 监测对象类数据的做法,但凡数据改变了,setter 就发挥作用了;
Vue.set 的使用
先写个展示的页面:
<div id="root">
<h2>school:{{school}}</h2>
<h2>address:{{address}}</h2>
<hr>
<h3>uname:{{student.name}}</h3>
<h3>age:{{student.age}}</h3>
<h3>friends</h3>
<ul>
<li v-for="(f,index) in student.friends" :key="index">
{{f.name}}-{{f.age}}
</li>
</ul>
</div>
提示1:列表渲染,:key=“index”
;
提示2:访问 vm.a ,如果 a 不存在,会报错,如果 a.b ,a 存在 b 不存在的话,不会报错;
const vm = new Vue({
el:'#root',
data:{
school:'51CTO',
address:'51cto.com',
student:{
name:'Jack',
age:30,
friends:[
{name:'Jerry',age:28},
{name:'Tom',age:29}
]
}
}
})
提示1:数据中使用了多级属性;
看下效果:
此时,要后期给学员 "Jack",加上性别属性该怎么做?
添加数值属性
<h3>gender:{{student.gender}}</h3>
能否在控制台直接添加呢:
添加后并不会立即被渲染到页面上,因为 vm._data.student.gender ="male"
设置的属性是数据描述符,不是存取描述符(没有 getter 和 setter 存取器)。
添加响应式属性
通过 Vue.set(target,key,val)
方法,可以给 vm 添加存取器属性,这样添加的属性就具有响应式了;
这样一来,gender 属性也就有了响应式:
提示:vm.data
和vm._data
之间有数据代理的关系,也就是说:vm.student === vm._data.student
等价的;
Vue.set(vm._data.student,'gender','male')
// 等价于
Vue.set(vm.student,'gender','male')
除了 Vue.set()
方法,还可以使用 vm.$set()
方法来实现,这两种方法是一样的:
通过 vm.$set()
添加的属性就具有响应式了,添加成功后,会立即被渲染到页面上:
Vue.set() 和 vm.$set() 等价
需求:点击按钮,给学员添加性别属性;
<div id="root">
<h2>{{name}}</h2>
<button @click="addGender">点击添加性别:男</button>
<h3>姓名:{{student.name}}</h3>
<h3>年龄:{{student.age}}</h3>
<h3 v-if="student.gender">性别:{{student.gender}}</h3>
<h3>朋友们</h3>
<ul>
<li v-for="(f,index) in student.friends" :key="index">
{{f.name}}-{{f.age}}
</li>
</ul>
</div>
提示1:绑定点击事件,添加 v-if
条件渲染,条件是表达式: student.gender
有值就显示,没值不显示;
const vm = new Vue({
el:"#root",
data:{
name:'学生信息',
student:{
name:"张三",
age:29,
friends:[
{name:'李四',age:30},
{name:'王五',age:32}
]
}
},
methods: {
addGender(){
// Vue.set(this.student,'gender','男')
this.$set(this.student,'gender','男') // vm.$set() 添加响应式属性
}
},
})
提示:通过 vm.$set()
或 Vue.set()
方法都可以给对象添加响应式属性;
注意:Vue.set()
和 vm.$set()
不能直接给 vm 和根添加属性;
比如:要添加学校信息,可以先建一个 school 的属性,把 leader 放在 school 里面;
const vm = new Vue({
el:"#root",
data:{
school:{
leader:"tom",
age:50
},
student:{//...}
}
})
这样就可以给 school 和 student 分别添加响应式属性了;