前言 在前端开发过程中,最为注重的就是数据的即时性和响应。但随着技术的发展vue2.0的数据响应方式,不能响应属性的新增和删除、以及通过数组下标修改界面不会自动更新等弊端逐渐显露。vue3.0为开发者提供了更为便捷的数据响应方式,接下来就让我们一起去探索一下。
目标
1
基本数据
类型与复杂数据
类型如何定义响应式数据 2ref、reactive
数据响应式原理 3 vue2.0数据响应方式与弊端 4. proxy数据响应原理
graph LR
A[数据响应式] --> B[学习vue3.0数据响应]
B-->D[基本用法与用到的底层方法]
B-->F[proxy如何实现数据代理]
B -->E[vue3.0为什么使用reflect对源对象属性操作]
A --> C[vue2.0与vue3.0数据响应原理总结]
C --> G[回顾vue2.0数据响应式]
C --> H[vue3.0数据响应式]
学习vue3.0数据响应
1 基本用法与用到的底层方法
1.1 ref
ref 用于将
基本类型
数据变成响应式
import {ref} from 'vue'
setup(){
// ref处理数据
let count = ref(0)
let name = ref('张三')
function addCount(){
// 修改数据要用.value,
// 模版字符串中不用.value 因为vue3 自动的给我们做了处理
count.value++
console.log('---------count---------',count)
console.log('---------name---------',name)
}
return{
count,
name,
addCount
}
}
经过ref
处理过后的基本数据类型,数据会包裹在RefImpl
中,当数据发生改变就会触发propotype下get、set
==》 底层Object.definepropoty的getter setter
。
ref不能定义复杂类型数据吗,废话不多,直接上手来try一下
import {ref} from 'vue'
setup(){
let person = ref({
name:'张三',
age:'1111',
sex:'男'
})
function addCount(){
console.log('---修改前person---',person,person.value,person.value.name)
person.value.name='小明'
console.log('---修改后person---',person,person.value,person.value.name)
}
return{count,name,addCount}
}
结果是页面中的数据是响应式的,说明ref是可以处理复杂数据类型
既然ref已可以满足,vue3为啥还设计了一个reactive方法?
看一下控制台打印的先是RefImpl RefImpl .value才是一个Proxy
对象
用reactive
处理后的数据 是proxy对象
(数据响应的底层原理是ES6的proxy),ref底层也是自动的把复杂数据类型进行了reactive
处理
总结来说:ref
底层还是用reactive
对复杂数据对象进行了处理。
1.2 reactive
reactive
专门用来处理复杂数据
对象的方法
import { ref,reactive } from 'vue'
setup(){
let count = ref(0)
let name = ref('张三')
let person = reactive({
name:'张三',
age:'1111',
sex:'男',
school:{
name:'北京小学',
class:{
id:'一年级'
}
}
})
function addCount(){
count.value++
console.log('---修改前person---',person,person,person.name)
person.school.class.id ='小学'
console.log('---修改后person---',person,person,person.name)
}
return{
count,
name,
person,
addCount
}
}
}
reactive
数据响应式底层实现原理是es6的proxy
reactive可以处理基本数据吗,废话不多,直接上手来try一下
let count = reactive(0)
let name = reactive('张三')
定义完成,控制台直接报错,reactive无法处理基本数据
1.3 reactive与ref对比
从定义数据角度
- ref用来定义:
基本数据
类型数据 - reactive:
对象(或数据)
类型数据 - ref可定义
对象(或数据)
类型数据,但它内部会自动通过reactive转为代理对象
从原理角度 - ref通过
Object.defineProperty
的get
与set
来数显响应式(数据劫持) - reactive通过使用
Proxy
来实现响应式(数据劫持),再通过Reflect
操作源对象
内部数据 从使用角度 - ref定义的数据:操作数据需要
.value
,读取数据时模板中直接读取不需要
.value - reactive定义的数据:
.key
.键名
就可使用
2 proxy数据代理
Proxy
是es6中提供的一个代理方法,可以在js中直接使用无需引用
下面我们用proxy来手写一个 模拟一下vue3.0实现响应式
将对象封装到proxy中,当对对象中的属性
进行增删改查
时,会触发Proxy下的get、set以及deleteProperty
,这样就能就监听到 数据的变化
,从而更新数据变化实现数据响应式。
<script>
let person = {
name:'张三',
age:"18"
}
let p = new Proxy(person,{
// 读取属性时调用
get(target,key){
console.log(`-----获取${key}的值-----`)
return target[key]
},
// 修改添加属性时调用
set(target,key,value){
console.log(`-----修改/添加${key}的值为${value}-----`)
// 操作原数据
target[key] = value
return true
},
// 删除属性时调用
deleteProperty(target,key){
console.log(`-----删除${key}-----`)
return delete target[key]
}
})
</script>
Object.definePropety()
相比Proxy
无法监听到属性增加和删除,Proxy
对属性的任何操作都能监听到
3 为什么使用reflect对源对象属性操作
上面 模拟的实现了vue3.0数据响应式,但是vue3.0真正的底层却不是这样写的,它借助了Reflect(反射)来处理源
数据。
Reflect是什么?,Reflect
是ES6提供的一种数据反射方法,很多Object的方法在Reflect上都可以使用,例如defineProperty。
Vue3.0中用Proxy
与 Reflect
实现数据的响应式
使用Reflect去更改源
对象的值
let person = {
name:'张三',
age:"18"}
let p = new Proxy(person,{
// 读取属性时调用
get(target,key,receiver){
console.log(`-----获取${key}的值-----`,receiver==p)
return Reflect.get(target,key)
},
// 修改添加属性时调用
set(target,key,value){
console.log(`-----修改/添加${key}的值为${value}-----`)
Reflect.set(target,key,value)
},
// 删除属性时调用
deleteProperty(target,key){
console.log(`-----删除${key}-----`)
return Reflect.deleteProperty(target,key)
}
})
既然不用Reflect已经能实现数据响应式了,为什么还要多此一举用Reflect
来处理源
数据?
下面就从一下两个方面来具体说明
3.1 健壮性
Object.defineProperty(person,'SEX',{
get(){
return '男'
},
})
Object.defineProperty(person,'SEX',{
get(){
return '女'
},
})
可以看到,用Object.defineProperty重复的定义属性的时候 报错了
,且因为JS是单线程的,导致程序整个挂断,这不是我们想看到的情况,但是若在底层中写大量的 try catch 不太优雅。
如何解决这个问题呢?,Reflect
可以完美的解决这个问题,我们来看一下
const sex1 = Reflect.defineProperty(person,'SEX',{
get(){
return '男'
},
})
const sex2 = Reflect.defineProperty(person,'SEX',{
get(){
return '女'
},
})
console.log(sex1)
console.log(sex2)
if(sex2){
console.log('sex2为true,执行成功')
// 业务逻辑
}else{
console.log('sex2为false,执行失败')
// 业务逻辑
}
Reflect
重复定义不会导致整个程序挂掉,且Reflect.defineProperty
会返回一个是否执行成功的Boolean
值,可以根据此值进行相对应的业务处理。
3.2 改变this的上下文指向
Proxy
的get、set有一个参数receiver
,receiver究竟指的是什么?
通过console.log可看出 receiver==p p是Proxy的实例对象,receiver指向了Proxy的实例对象,即调用的指向
Reflect.get中有第三个参数receiver,Reflect.get(target,key,receiver)可以将访问对象的this
指向修改为receiver对象
let p = new Proxy(person,{
// 读取属性时调用
get(target,key,receiver){
console.log(`-----get receiver是否等于p-----`,receiver==)
return Reflect.get(target,key,receiver)
},
// 修改添加属性时调用
set(target,key,value,receiver){
console.log(`-----set receiver是否等于p-----`,receiver==p)
Reflect.set(target,key,value,receiver)
},
})
vue2.0与vue3.0数据响应原理总结
1 回顾vue2.0数据响应式
vue2数据响应式原理可参考vue2数据检测原理 实现原理
- 对象类型: 通过
Object.definePropety()
对属性的读取、修改进行拦截(数据劫持) - 数组类型: 通过
重写更新
数组的一系列方法来实现拦截。(对数组的变更方法进行包装)
Object.defineProperty(data,'count',{
get () ,
set () {}
})
- 其他:
Vue.set()
、this.$set()
等
存在问题:
新增属性、删除属性
,界面不会更新直接通过下标修改数组
,界面不会自动更新
2 Vue3.0的响应式
实现原理
- Proxy (代理) 拦截对象中任意属性的变化,包括: 属性值的读写、属性的添加、属性的删除等
- 通过Reflect(反射) 对
源
对象的属性进行操作. 。 - MDN文档中描述的Proxy与Reflect: Proxy
总结
ref
一般用于基本数据类型,也可以用于复杂数据类型,但还是建议复杂直接使用reactive。reactive
用于复杂数据类型实现响应式,不可以用于基本数据类型。Proxy
(代理)和Reflect
可以拦截对象中属性的增删改查
,比vue2.0中的Object.definePropety
更加方便灵活。
到这里有关vue3.0的响应式原理已经结束,可能看完本文还是有点模糊迷瞪,没关系你可以试着敲一下代码。自己尝试一下会有更加深刻的理解
参考链接 vue2数据检测原理