浅析Vue2和Vue3响应式原理及两者差异
目录vue2的响应式
- 对象类型:通过
Object.defineProperty()
对属性的读取、修改进行拦截(数据劫持) - 数组类型:通过重写更新数组的一系列方法来实现拦截(对数组的变更方法进行了包裹)
vue2中为数据添加响应式
- 添加属性:无法直接添加,需要使用set额外设置响应式,否则无法被渲染
- 删除属性:无法直接删除,需要使用delete删除,否则无法被渲染
- 修改属性:对于已设置响应式的数据,可以自由修改;对于数组内的数据,必须使用数组提供的方法,否则无法被渲染
<template>
<div>
<h1>Vue2写的效果</h1>
<h2>姓名:{{person.name}}</h2>
<h2>年龄:{{person.age}}</h2>
<h2 v-show="person.sex">性别:{{person.sex}}</h2>
<h2>爱好:{{person.hobby}}</h2>
<button @click="addSex">添加一个sex属性</button>
<button @click="deleteName">删除name属性</button>
<button @click="changeSomething">修改name属性和hobby属性</button>
</div>
</template>
<script>
import Vue from 'vue'
export default {
name: 'App',
data() {
return {
person:{
name:'张三',
age:18,
hobby:['学习','吃饭']
}
}
},
methods:{
addSex(){
//属性确实被添加了,但是无法在页面中显示,因为这个属性没有响应式
this.person.sex = '女' //无效
//添加响应式数据的方法1
this.$set(this.person,'sex','女') //有效
// 添加响应式数据的方法2
Vue.set(this.person,'sex','女') //有效
},
deleteName(){
//属性确实被添加了,但是无法在页面中显示,【即便】这个属性具有响应式
delete this.person.name //无效
//移除响应式数据的方法1
this.$delete(this.person,'name') //有效
//移除响应式数据的方法2
Vue.delete(this.person,'name') //有效
},
changeSomething(){
//已经具有响应式的数据可以直接修改
this.person.name = '李四' //有效
//但是如果数据存储在数组中,就必须使用数组的方法修改
this.person.hobby[0] = '打游戏' //无效
this.$set(this.person.hobby,0,'打游戏') //有效
this.person.hobby.splice(0,1,'打游戏') //有效
}
}
}
</script>
模拟Vue2实现响应式原理
let person = {
name:'张三',
age:18
}
//模拟vue2实现响应式
let p = {}
Object.defineProperty(p,'name',{
configurable:true,
//读取name时调用
get(){
return perosn.name
},
//修改name时调用
set(value){
console.log("name属性被修改,更新页面");
person.name = value
}
//vue2响应式到这里就结束了,defineProperty无力支持删除操作,需要额外使用Vue.delete来删除响应式数据
})
- 关于Vue2响应式原理更深层次的理解可以参考:https://jishuin.proginn.com/p/763bfbd56cc9
vue3的响应式
- 通过Proxy代理:拦截对象中任意属性的变化
- 通过Reflect反射:对被代理对象中的属性进行操作
vue3中为数据添加响应式
- 只要数据被reactive包裹,所有的增删改查都可以直接操作,不用考虑响应式
<template>
<div>
<h1>Vue3写的效果</h1>
<h2>姓名:{{person.name}}</h2>
<h2>年龄:{{person.age}}</h2>
<h2 v-show="person.sex">性别:{{person.sex}}</h2>
<h2>爱好:{{person.hobby}}</h2>
<button @click="addSex">添加一个sex属性</button>
<button @click="deleteName">删除name属性</button>
<button @click="changeHobby">修改hobby属性</button>
</div>
</template>
<script>
import {reactive} from 'vue'
export default {
name: 'App',
setup() {
let person = reactive({
name:'张三',
age:18,
hobby:['学习','吃饭']
})
//所有的增删改查都可以直接操作
function addSex() {
this.person.sex = '女'
}
function deleteName(){
delete this.person.name
}
function changeHobby(){
this.person.hobby[0] = '打游戏'
}
return{
person,
addSex,
deleteName,
changeHobby,
}
}
}
</script>
模拟vue3实现响应式
//使用了es6新增的Proxy,对于p的所有改变都会被反馈到person身上
const p = new Proxy(person,{
//读取数据,比如在p.sex的时候,propName=sex,这样就可以方便的读取任意属性
get(target,propName){
console.log(`读取了p上的${propName}属性`)
return Reflect.get(target,propName)
},
//添加or修改数据
set(target,propName,value){
console.log(`修改了p上的${propName}属性,更新界面`)
return Reflect.get(target,propName,value)
},
//删除数据
deleteProperty(target,propName){
console.log(`删除了p上的${propName}属性,更新界面`);
return Reflect.deleteProperty(target,propName)
}
})
- 关于Vue3响应式原理更深层次的理解可以参考:https://segmentfault.com/a/1190000041540939
对比Vue2和Vue3的响应式
- 响应式主要就是通过数据劫持,依赖收集,派发更新的方式来实现的
-
Vue2无法检测到直接添加的新数据,需要使用$set方法添加响应式
Vue3可以自动检测到被reactive包裹的对象内的,直接添加的新数据,无需手动添加响应式;当然也可以逐个使用reactive和ref手动添加
-
Vue2无法检测到直接删除的新数据,需要使用$delete方法添加响应式
同1,Vue3可以自动检测到被删除的新数据
-
Vue2无法检测到数组内部元素的更新,因为数组元素内部元素不具有响应式,必须通过数组提供的方法修改才能检测到
Vue3可以自动检测到数组内部元素的更新
-
对于基本数据类型,Vue2和Vue3基本都通过
Object.defineProperty()
完成响应式添加对于对象类型,Vue2仍旧采用
Object.defineProperty()
,Vue3则采用reactive(或者说Proxy)添加响应式