在第二篇文章中举例的时候,我们创建了非响应式的数据,在控制台中查看数据发现并不存在get与set方法。在查了文档后发现,Vue3中需要使用一个新的方法 `ref ` 去让数据变成响应式的。
我叫{{name}},我今年{{age}}了
话虽这么说,但是为数据添加上 ref 后点击按钮去修改信息,页面上并没有任何变化。为了找到出错的原因,在changeInfo之前先输出name与age试试。
原来经过ref加工后基本数据变成了一个对象,它是Ref(reference)Impl(implement)的实例对象。那我们可以考虑,如何拿到经过ref包装的引用实现的实例对象(即引用对象)中包含的数据。
我们将刚刚打印的内容展开来看,可以清晰的看到被包装的name属性是存在get和set的。如果想读到数据,那么就需要通过.value去获取,修改的话也需要这样。
function changeInfo () {
name.value = '白猫',
age.value = 99
}
在Vue3中,ref()方法是基于Object.defineProperty与get、set方法去实现的数据劫持。
如果想对 ref 包装的数据进行处理,需要对数据.value后再操作,那么对于包装后的对象数据类型数据,也应当需要.value拿到具体对象数据。在Vue2中我们知道,即使一个对象中嵌套了多个对象,Vue都能通过循环遍历,从外到最深处设置响应式。
那么在Vue3中面对一个people对象的时候,我们是否需要在通过people.value拿到实例对象后,继续people.value.name.value去拿到姓名这个属性。
{{people.name}},是{{people.type}}
也许在你心中是这么想的:将对象通过 ref 封装后,里面的value也需要进行ref封装,然后获取每一项value的时候都要调用具体的get与set方法。
经过打印后发现,其实是不需要的。通过第一次的.value后,获取对象中的数据便不需要再执行.value这个操作,直接调用即可。
不必再继续调用.value的原因在于 ref 检测到对象数据类型的数据时,当.value获取到了对象实例本身后,调用了另外一个方法 `reactive` 去包装{}内的数据,该方法是基于Proxy代理实现的。当我们直接用reactive去包裹数据对象的时候甚至可以省略第一个.value。
在前面介绍过Vue3的ref函数是通过Object.defineProperty去实现响应式的,reactive则是通过proxy来实现。首先介绍一下它的优点
1. 可以直接为对象添加不存在的属性/删除属性
2. 可以直接通过索引下标去修改数组中的元素数据
不难看到,这恰好弥补了Vue2中响应式原理的缺陷https://blog.csdn.net/flow_camphor/article/details/120657910
在这里我们单独创建一个html文件,通过window.proxy去了解一下原理。
Document
在控制台中测试后发现,通过改变p对象中元素的数据后,people中的数据也会得到改变。
这样的实现叫做数据劫持,还不能称为响应式。我们接下来需要捕获到这样修改数据的行为。
const p = new Proxy(people,{
// 读取p身上的某个属性时调用
get(target,propName){
// 这里通过[]获取元素的原因在于,Proxy中get方法的第二个参数
// 返回的是一个字符串,对象无法通过 . 的方式使用字符串
return target[propName]
},
// 修改p身上的某个属性、或者是给p追加某个属性时调用
set(target,propName,value){
target[propName] = value
},
// 删除p身上某个属性时调用
deleteProperty(target,propName){
return delete target[propName]
}
})
这样做实现了最为基础的响应式修改,但是这样太简洁了,其实并非Vue3中所使用的方法,接下来我们先看看一个新的知识点Reflect反射。这个是es6中新增的内容,其实现在ECMA正在尝试将Object中的内容全部移植到Reflect中,用Reflect代替Object是一个趋势,对于框架来说,直接通过一个对象去修改数据是可以轻松很多的,因此将上面的代码用Reflect替换。
const p = new Proxy(people,{
// 读取p身上的某个属性时调用
get(target,propName){
return Reflect.get(target,propName)
},
// 修改p身上的某个属性、或者是给p追加某个属性时调用
set(target,propName,value){
Reflect.set(target,propName,value)
},
// 删除p身上某个属性时调用
deleteProperty(target,propName){
return Reflect.deleteProperty(target,propName)
}
})