目录
- 一、修改数据时的一个问题
- 1.1 现象一
- 1.2 现象二
- 二、Vue监测数据原理
- 2.1 模拟一个数据监测
- 2.2 数据劫持
- 2.3 Vue.set()/vm.$set()
- 2.4 基本原理
- 2.4.1 如何监测对象中的数据?
- 2.4.2 如何监测数组中的数据?
- 2.4.3 修改数组中的某个元素
- 2.5 案例
- 2.5.1 需求功能
- 2.5.2 实现
一、修改数据时的一个问题
下面案例中,我们定义了一个按钮,期望是:点击按钮更新persons[0]
的信息。
但实际问题是:Vue
监测不到数据的变化。
<div id="root">
<h2>列表过滤</h2>
<button @click="change">更新令狐冲信息</button>
<ul>
<li v-for="(p,index) in persons" :key="p.id" >
{{p.name}} - {{p.use}} - {{p.age}}
</li>
</ul>
</div>
<script>
const vm = new Vue({
el:'#root',
data:{
persons:[
{id:'001',name:'令狐冲',use:"独孤九剑",sex:'男',age:20},
{id:'002',name:'任盈盈',use:"仙女下凡",sex:'女',age:19},
{id:'003',name:'任我行',use:"吸星大法",sex:'男',age:50}
]
},
methods: {
change(){
// 可行的方式
// this.persons[0].name = "假的令狐冲";
// this.persons[0].use = "葵花宝典";
// this.persons[0].age = 100;
// Vue没发现你修改了数据,其实已经改了
this.persons[0] = {id:'001',name:'假的令狐冲',use:"葵花宝典",sex:'男',age:100}
}
},
})
</script>
1.1 现象一
- 在浏览器中打开页面,在打开
vue-devtools
- 点击按钮
- 观察工具中的数据,
竟然没有发生变化!!!
但其实数据已经真实改变了 - 我们可以通过控制台,输入
vm.persons[0]
查看,发现数据确实变了。
1.2 现象二
- 在浏览器中打开页面,然后先点击按钮
- 再打开
vue-devtools
- 查看工具中数据,发现
数据改变了!!!
二、Vue监测数据原理
2.1 模拟一个数据监测
下面案例中,当data
的数据发生变化,就会在控制台打印出一句话。
let data = {
name:'令狐冲',
menpai:'华山派'
}
// 创建一个监视的实例对象,用于监视data中属性的变化
const obs = new Observer(data)
// 准备一个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
}
})
})
}
2.2 数据劫持
数据劫持
:在访问或修改对象的某个属性时,通过一段代码拦截这个行为,进行额外的操作,如修改返回结果。
Vue 2.x
使用的是 Object.defineProperty()
,而 Vue
在 3.x
版本之后改用 Proxy
进行实现
2.3 Vue.set()/vm.$set()
-
语法:Vue.set(
target
,propertyName/index
,value
) -
功能:向
响应式对象
中添加一个property
,并确保这个新property
同样是响应式
的,且触发视图更新
。 -
注意:它必须用于向
响应式对象上
添加新 property
,因为 Vue无法探测普通的新增 property (比如this.myObject.newProperty = 'hi'
)
Vue.set(this.person.hobby, 0, "哈哈哈")
this.$set(this.person.hobby, 0, "哈哈哈")
// 从第0个位置,删一个,在插入一个
// this.person.hobby.splice(0,1,"哈哈哈")
2.4 基本原理
Vue
监测到data
中的数据(所有层次的数据
)发生变化,就会调用setter
重新解析模板,解析引用的到的数据的值。
2.4.1 如何监测对象中的数据?
-
通过
setter
实现监视,且要在new Vue
时就传入要监测的数据。(1). 对象中
后追加的属性
,Vue默认不做响应式处理
(2). 如需给后添加的属性
做响应式,请使用如下API
:Vue.set(target, propertyName/index,value) vm.$set(target, propertyName/index,value)
2.4.2 如何监测数组中的数据?
Vue
通过包裹数组的更新元素的方法
实现,本质就是做了两件事:
- (1). 调用原生对应的方法对数组进行更新。
- (2). 重新解析模板,进而更新页面。
数组中这些被包裹过的方法
包括:
push()
pop()
shift()
unshift()
splice()
sort()
reverse()
2.4.3 修改数组中的某个元素
在Vue
中,修改数组中的某个元素一定要用如下方法,才能实现响应式:
被包裹过的方法
:push()、pop()、shift()、unshift()、splice()、sort(). reverse()- 通过
Vue.set()
或vm.$set()
注意:Vue.set()
和 vm.$set()
不能给vm
或 vm的根数据对象(vm.data)
添加属性。
2.5 案例
2.5.1 需求功能
- 需求如下截图
2.5.2 实现
<div id="root">
<h2>笑傲江湖</h2>
<hr>
<div>
<button @click="person.age++">年龄+1岁</button>
<button @click="addSex">添加性别属性,默认值:男</button>
<button @click="addFriend">在列表首位添加一个朋友</button>
<button @click="updateFName">修改第一个朋友的名字为:小三</button>
<button @click="addHobby">添加一个爱好</button>
<button @click="updateHobby">修改第一个爱好为: 哈哈哈</button>
<button @click="removeHobby">过滤爱好中的喝酒</button>
</div>
<h3>姓名:{{person.name}}</h3>
<h3>年龄:{{person.age}}</h3>
<h3 v-if="person.sex">性别:{{person.sex}}</h3>
<h3>爱好:</h3>
<ul>
<li v-for="(h,index) in person.hobby" ::key="index">
{{h}}
</li>
</ul>
<h3>朋友:</h3>
<ul>
<li v-for="(f,index) in person.friends" ::key="index">
{{f.name}} - {{f.age}}
</li>
</ul>
</div>
<script>
const vm = new Vue({
el:'#root',
data:{
person:{
name:'令狐冲',
age:26,
hobby:['喝酒','练剑','行侠仗义'],
friends:[
{name:'田伯光',age:30},
{name:'任我行',age:50}
]
}
},
methods: {
addSex(){
// Vue.set(this.person,'sex','男')
this.$set(this.person,'sex','男')
},
addFriend(){
this.person.friends.unshift({name:'任盈盈',age:'26'})
},
updateFName(){
this.person.friends[0].name = '小三'
},
addHobby(){
this.person.hobby.push('大笑')
},
updateHobby(){
// 从第0个位置,删一个,在插入一个
// this.person.hobby.splice(0,1,"哈哈哈")
// Vue.set(this.person.hobby, 0, "哈哈哈")
this.$set(this.person.hobby, 0, "哈哈哈")
},
removeHobby(){
this.person.hobby = this.person.hobby.filter((h)=>{
return h !== "喝酒"
})
}
},
})
</script>