一、更新时的一个问题
如果我要点击按钮实现更新冯万宁儿的信息,那么如果一个属性一个属性地改,可以修改成功,并且Vue也会检测到并更新到页面。
但是我如果直接把要修改的信息写在this.persons[0] = { id: 001, name: '冯千宁儿', age: 66, sex: '女' };这里,就不行,Vue监测不到并且不更新到页面,这是为什么?
<!-- 准备好一个容器 -->
<div id="root">
<h1>人员列表</h1>
<button @click="updateNing">点击更新冯万宁儿</button>
<ul>
<li v-for="(p,index) in persons" :key="p.id">
{{p.name}}----{{p.age}}----{{p.sex}}
</li>
</ul>
</div>
<script>
const vm = new Vue({
el: '#root',
data: {
keyword: '',
persons: [
{ id: 001, name: '冯万宁儿', age: 23, sex: '男' },
{ id: 002, name: '屁及万儿', age: 18, sex: '男' },
{ id: 003, name: '及丽热巴', age: 10, sex: '女' },
{ id: 004, name: '冯小刚儿', age: 60, sex: '男' }
]
},
methods: {
updateNing() {
// this.persons[0].name = '冯千宁儿'; //奏效
// this.persons[0].age = 66; //奏效
// this.persons[0].sex = '女'; //奏效
this.persons[0] = { id: 001, name: '冯千宁儿', age: 66, sex: '女' };
//上面这么写,也奏效,数据实际上已经改了,但是Vue监测不到所以没更新到页面,为啥捏?
}
}
})
</script>
二、原生js模拟Vue监视data对象中的数据
用原生js写一个Vue监视data数据改变的效果
可以看出,Vue监视数据的原理,就是靠setter
<script type="text/javascript">
let data = {
name : 'cgp',
age : 18
}
// 创建一个监视的实例对象,用于监视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
}
})
})
}
</script>
这只是个模拟,实际上Vue做了更多事情,比如:
1、在setter中不是简单的输出,而是重新解析模板,生成虚拟DOM,新旧虚拟DOM对比,更新页面实现响应式。
2、Vue中vm还对_data做了数据代理,我可以直接vm.name,不用vm._data.name
3、我这么写,如果数据是套娃的(对象中还有对象还有对象),就监测不到。但是Vue就可以,不管你数据藏得多深,Vue都能找到,每个数据都有自己的getter和setter。只要有数据被改,就会实现响应式。(这里面好像涉及了深拷贝的知识?
三、Vue监测对象中的数据
1、监视对象中的数据
1、vue会监视data中所有层次的数据,不管你藏得有多深。
2、如何监测对象中的数据?
通过setter实现监视,且要在new Vue时就传入要监测的数据。
(1).对象中后追加的属性,Vue默认不做响应式处理
(2).如需给后添加的属性做响应式,请使用如下API:
Vue.set(target,propertyName/index,value) 或
vm.$set(target,propertyName/index,value)
2、Vue.set的使用
用法:
向响应式对象中添加一个 property,并确保这个新 property 同样是响应式的,且触发视图更新。它必须用于向响应式对象上添加新 property,因为 Vue 无法探测普通的新增 property (比如 this.myObject.newProperty = 'hi'
)
注意对象不能是 Vue 实例,或者 Vue 实例的根数据对象。
_data在收集data中的数据之前,先做了一个加工(这个加工也可以称之为数据劫持),那就是给每个数据匹配一个getter和setter,前面说了,Vue监视数据的原理,就是靠setter。所以如果我们后期手动直接给_data添加属性(注意区别手动添加和从data中收集),是无法实现响应式的,因为没办法给它setter,只有从data中收集过来的属性才能通过一个鱿鱼西定义的好像是叫Observer什么什么的方法给它搞个setter。
Vue.set()是有局限性的!!!它只能给data中的某个对象追加属性,不能给vm 或 vm的根数据对象(data或_data) 添加属性!!!
也就是说第一个参数不能是 Vue 实例,或者 Vue 实例的根数据对象(_data)
例如给学生添加性别:
<div id="root">
<h1>学校信息</h1>
<h2>学校名称:{{name}}</h2>
<h2>学校地址:{{address}}</h2>
<hr>
<h1>学生信息</h1>
<button @click="add">添加性别</button>
<h2>学生姓名:{{student.name}}</h2>
<h2 v-if="student.sex">学生性别:{{student.sex}}</h2>
<h2>学生年龄:真实{{student.age.rAge}}, 对外{{student.age.sAge}}</h2>
<h2>朋友</h2>
<ul>
<li v-for="(f,index) in student.friends" :key="index">
{{f.name}}----{{f.age}}
</li>
</ul>
</div>
<script>
const vm = new Vue({
el:'#root',
data:{
name:'椰果',
address:'郑州',
student:{
name:'cgp',
age:{
rAge:'18',
sAge:'19'
},
friends:[
{name:'yhg',age:'20'},
{name:'zym',age:'21'}
]
}
},
methods:{
add(){
// Vue.set(this.student,'sex','男')
this.$set(this.student,'sex','男')
}
}
})
</script>
四、Vue监测数组中的数据
1.如何监测数组中的数据?
其实是Vue里面定义了一个非常牛逼的方法,目的只有一个:实现响应式。
方法里包装的代码本质就是做了两件事:
(1)调用原生对应的方法对数组进行更新。然后第二步在这个基础上再加点料
(2)重新解析模板,生成虚拟DOM,diff算法……进而更新页面,实现响应式。
所以在Vue修改数组中的某个元素实现响应式一定要用如下方法:
(1)使用这些API:push()、pop()、shift()、unshift()、splice()、sort()、reverse()
(2)Vue.set() 或 vm.$set()也可以实现响应式,第二个参数写索引,第三个写元素,可以改也可以加
2、例如
<div id="root">
<h2>我的名字:{{name}}</h2>
<h2>我的年龄:{{age}}</h2>
<hr>
<h2>他的名字:{{boyfriend.name}}</h2>
<button @click="addHobby">点击替换'跳'的爱好</button>
<h2>爱好</h2>
<ul>
<li v-for="(h,index) in girlfriend.hobby" :key="index">
{{h}}
</li>
</ul>
</div>
<script>
const vm = new Vue({
el: '#root',
data: {
name: 'cgp',
age: 18,
girlfriend: {
name: 'ht',
hobby: ['唱', '跳', 'rap']
}
},
methods: {
addHobby() {
//除了那7个方法外,set方法也可以改变数组实现响应式
Vue.set(this.girlfriend.hobby, 1, '打游戏');
}
},
})
</script>
五、回头看看第一个问题
说了这么多,再回头看第一个问题,就恍然大悟了
1、单独改每个属性可以奏效,是因为Vue可以监测到对象里的每个属性,不管它藏的多深,只要是个对象,就有相应的getter和setter
2、将数组中的元素单独替换,数据修改成功,但是Vue没有监测到,页面不显示,这是因为数组中的每个元素是没有getter和setter的,如果修改数组中的元素无法实现响应式.
3、要想让更改数组中的元素实现响应式,就得调用数组的七个api或者用Vue.set()方法
<!-- 准备好一个容器 -->
<div id="root">
<h1>人员列表</h1>
<button @click="updateNing">更新冯万宁儿</button>
<ul>
<li v-for="(p,index) in persons" :key="p.id">
{{p.name}}----{{p.age}}----{{p.sex}}
</li>
</ul>
</div>
<script>
const vm = new Vue({
el: '#root',
data: {
persons: [
{ id: 001, name: '冯万宁儿', age: 23, sex: '男' },
{ id: 002, name: '屁及万儿', age: 18, sex: '男' },
{ id: 003, name: '及丽热巴', age: 10, sex: '女' },
{ id: 004, name: '冯小刚儿', age: 60, sex: '男' }
],
},
methods: {
updateNing() {
// this.persons[0].name = '冯千宁儿'; //奏效
// this.persons[0].age = 66; //奏效
// this.persons[0].sex = '女'; //奏效
// this.persons[0] = { id: 001, name: '冯千宁儿', age: 66, sex: '女' };
//上面这么写,也奏效,数据实际上已经改了,但是Vue监测不到所以没更新到页面,为啥捏?
//实际上是因为Vue监测数组时,数组没有相应的setter和getter,直接改Vue监测不到
//但是数组中的每个对象中的属性都有自己的getter,setter,所以单独改属性时可以监测到的
//要想让更改数组中的元素实现响应式,就得调用数组的七个api或者用set
this.persons.splice(0, 1, { id: 001, name: '冯千宁儿', age: 66, sex: '女' });
}
}
})
</script>
六、练习
<div id="root">
<button @click="addSex">添加一个性别属性,默认为女</button>
<button @click="addHeight">添加一个身高属性,默认为170</button><br>
<button @click="girlfriend.age.realAge--">年龄-1</button>
<button @click="addFriend">在列表前加一个朋友</button><br>
<button @click="updateFriend">修改第一个朋友的名字为张三</button>
<button @click="addHobby">添加一个爱好</button><br>
<button @click="updateHobby">修改第一个爱好为散步</button>
<button @click="removeHobby">过滤掉爱好中的跳</button>
<h2>名字:{{girlfriend.name}}</h2>
<h2>年龄:对外{{girlfriend.age.fakeAge}},真实{{girlfriend.age.realAge}}</h2>
<h2 v-if="girlfriend.sex">性别:{{girlfriend.sex}}</h2>
<h2 v-if="girlfriend.height">身高:{{girlfriend.height}}</h2>
<h2>朋友们</h2>
<ul>
<li v-for="p in girlfriend.friends" :key="p.id">
{{p.name}}----{{p.age}}
</li>
</ul>
<h2>爱好</h2>
<ul>
<li v-for="(h,index) in girlfriend.hobby" :key="index">
{{h}}
</li>
</ul>
</div>
<script>
const vm = new Vue({
el: '#root',
data: {
name: 'zzy',
age: 18,
girlfriend: {
name: 'ht',
// sex: '女',
age: {
realAge: 23,
fakeAge: 18
},
friends: [
{ id: 001, name: 'jack', age: 10 },
{ id: 002, name: 'rose', age: 8 },
],
hobby: ['唱', '跳', 'rap']
}
},
methods: {
addSex() {
Vue.set(this.girlfriend, 'sex', '女');
},
addHeight() {
this.$set(this.girlfriend, 'height', 170);
},
addFriend() {
this.girlfriend.friends.unshift({ id: 003, name: 'alice', age: 5 }); //有效写法
},
updateFriend() {
this.girlfriend.friends[0].name = '张三';
},
addHobby() {
this.girlfriend.hobby.push('打游戏');
},
updateHobby() {
// this.girlfriend.hobby[0] = '散步'; //无效写法
// this.girlfriend.hobby.splice(0, 1, '散步'); //有效写法
Vue.set(this.girlfriend.hobby, 0, '散步'); //有效写法
},
removeHobby() {
// 变更方法,顾名思义,会变更调用了这些方法的原始数组。相比之下,也有非变更方法
// 例如 filter()、concat() 和 slice()。它们不会变更原始数组,而总是返回一个新数组。
// 当使用非变更方法时,可以用新数组替换旧数组:
this.girlfriend.hobby = this.girlfriend.hobby.filter((ele) => {
return ele !== '跳';
})
}
},
})
</script>
(ps:今天一个好消息补考过了!听懂了 也要再练习一下哦)