一、ref 包装基本数据类型数据
在第二篇文章中举例的时候,我们创建了非响应式的数据,在控制台中查看数据发现并不存在get与set方法。在查了文档后发现,Vue3中需要使用一个新的方法 `ref ` 去让数据变成响应式的。?
<template>
<div class="main">
<h1>我叫{{name}},我今年{{age}}了</h1>
<button @click="changeInfo">修改</button>
</div>
</template>
<script>
import {ref} from 'vue'
export default {
name: 'App',
setup () {
let name = ref('黑猫几绛')
let age = ref(20)
function changeInfo() {
name = '白猫',
age = 99
}
return {
name,
age,
changeInfo
}
}
}
</script>
话虽这么说,但是为数据添加上 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 包装对象数据类型数据
如果想对 ref 包装的数据进行处理,需要对数据.value后再操作,那么对于包装后的对象数据类型数据,也应当需要.value拿到具体对象数据。在Vue2中我们知道,即使一个对象中嵌套了多个对象,Vue都能通过循环遍历,从外到最深处设置响应式。
那么在Vue3中面对一个people对象的时候,我们是否需要在通过people.value拿到实例对象后,继续people.value.name.value去拿到姓名这个属性。
<template>
<div class="main">
<h3>{{people.name}},是{{people.type}}</h3>
<button @click="changeInfo">修改</button>
</div>
</template>
<script>
import {ref} from 'vue'
export default {
name: 'App',
// 最为原始的对象写法是这样,但是通过es6我们可以简写
// setup: function(){}
setup () {
let people = ref({
name: '黑猫几绛',
type: '学生'
})
function changeInfo() {
people.value.type.value = '打工人' // 报错
people.value.type = '打工人' // 正常修改
}
return {
changeInfo,
people
}
}
}
</script>
也许在你心中是这么想的:将对象通过 ref 封装后,里面的value也需要进行ref封装,然后获取每一项value的时候都要调用具体的get与set方法。
经过打印后发现,其实是不需要的。通过第一次的.value后,获取对象中的数据便不需要再执行.value这个操作,直接调用即可。
不必再继续调用.value的原因在于 ref 检测到对象数据类型的数据时,当.value获取到了对象实例本身后,调用了另外一个方法 `reactive` 去包装{}内的数据,该方法是基于Proxy代理实现的。当我们直接用reactive去包裹数据对象的时候甚至可以省略第一个.value。
<script>
import {reactive} from 'vue'
export default {
name: 'App',
setup () {
let people = reactive({
name: '黑猫几绛',
type: '学生'
})
function changeInfo() {
// 这里直接通过people.type就能拿到对象元素数据
people.type = '打工人'
}
return {
changeInfo,
people
}
}
}
</script>
三、Vue3响应式原理
在前面介绍过Vue3的ref函数是通过Object.defineProperty去实现响应式的,reactive则是通过proxy来实现。首先介绍一下它的优点
1. 可以直接为对象添加不存在的属性/删除属性
2. 可以直接通过索引下标去修改数组中的元素数据
不难看到,这恰好弥补了Vue2中响应式原理的缺陷https://blog.csdn.net/flow_camphor/article/details/120657910
在这里我们单独创建一个html文件,通过window.proxy去了解一下原理。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script type="text/javascript">
let people = {
name: '黑猫几绛',
type: '学生'
}
// proxy代理能够让p去映射people的操作与变化
// 在看文档的时候proxy第二个参数写了许多东西
// 这里我们先用一个空对象占位,其实就可以检测结果了
const p = new Proxy(people,{})
</script>
</body>
</html>
在控制台中测试后发现,通过改变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)
}
})
|