Vue3响应式原理
1. 回顾Vue2的响应式实现原理
- 对象类型: 通过Object.defineProperty()对属性的读取、修改进行拦截(数据劫持)
- 数组类型: 通过重写更新数组的一系列方法来实现拦截。(对数组的变更方法进行了包裹)。
js
Object.defineProperty(data, 'count', {
get () {},
set () {}
})
2. Vue2存在的问题
- 新增属性、删除属性,界面不会更新
- 直接通过下标修改数组,界面不会自动更新。 当然Vue也提供额外的api手动调用:
vue
<template>
<div>
<ul>
<li>名字: {{person.name}}</li>
<li>年龄: {{person.age}}</li>
<li>爱好: {{person.hobbyList}}</li>
<li v-if="person.sex">性别: {{person.sex}}</li>
</ul>
<button @click="addSex">新增性别</button>
<button @click="deleteSex">删除性别</button>
<button @click="updateHobby">更改爱好看片为学习</button>
</div>
</template>
<script>
import Vue from "vue";
export default {
name: 'App',
components: {},
data(){
return {
person: {
name:'张三',
age: 18,
hobbyList: ['吃饭', '看片']
}
}
},
methods: {
addSex(){
this.person.sex = '男'
// 解决办法
// this.$set(this.person, 'sex', '男')
// Vue.set(this.person, 'sex', '男')
},
deleteSex(){
delete this.person.sex
// 解决办法
// this.$delete(this.person, 'sex')
// Vue.delete(this.person, 'sex')
},
updateHobby(){
this.person.hobbyList[1] = '学习'
// 解决办法
// this.person.hobbyList.splice(1,1, '学习')
// this.$set(this.hobbyList, 1, '学习')
// Vue.set(this.hobbyList, 1, '学习')
}
}
}
</script>
Vue2数据响应式的局限性是因为Object.defineProperty()
中只有get set方法,不能动态监测add是因为他的api里面传入现有监测属性的名字,不能监测delete是因为没有监测delete的方法,而且比较繁琐的是每个现有属性的监测都需要使用编写Object.defineProperty()
。
js
// 源数据
let person = {
name: '张三',
age:18
}
// 模拟Vue2中实现响应式
let p = {}
Object.defineProperty(p, 'name', {
configurable:true, // 可以代理删除数据,但是没有delete()这种可以捕获的函数
get(){ // 有人调用读取name时候被调用
return person.name
},
set(value){ // 有人修改name时被调用
console.log('有人修改了name属性,我发现了,我要去更新页面!')
person.name = value
}
})
Object.defineProperty(p, 'age', {
get(){ // 有人调用读取age时候被调用
return person.age
},
set(value){ // 有人修改age时被调用
console.log('有人修改了age属性,我发现了,我要去更新页面!')
person.age = value
}
})
3. Vue3的响应式实现原理
通过Proxy(代理):拦截对象中任意属性的变化,包括:属性值的读写、属性的添加、属性的删除等。
通过Reflect(反射): 对源对象的属性进行操作。
MDN文档中描述的Proxy与Reflect:
Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。
Reflect 是一个内置的对象,它提供拦截 JavaScript 操作的方法。
Reflect
不是一个函数对象,因此它是不可构造的。
3.1 手动实现Vue3的proxy
js
// 模拟Vue3中的响应式
// Proxy在window上面,可以直接用, 使用Reflect获取操作对象属性,可以避免报错导致框架中try...catch泛滥影响代码质量
const p = new Proxy(person, {
// 读取属性被调用, target指向person
get(target, propName) {
console.log(`有人读取了p身上的${propName}属性`)
// return target[propName]
return Reflect.get(target, propName)
},
// 修改新增属性被调用
set(target, propName, newValue) {
console.log(`有人修改了p身上的${propName}属性, 我要去更新页面了`)
// target[propName] = newValue
Reflect.set(target, propName)
},
// 删除属性被调用
deleteProperty(target, propName) {
console.log(`有人删除了p身上的${propName}属性, 我要去更新页面了`)
// return delete target[propName]
return Reflect.deleteProperty(propName)
}
})