目录
前言
在我们学习Vue响应式之前,我们需要了解,Vue的响应式是什么,Vue响应式的怎么用,以及其内部深层次的原理。
一,响应式的理解
1.1 Mvvm模式的复习
Vue响应式的响应式又可以称之为数据双向绑定。双向绑定来源于有关Vue设计的Mvvm模式,这里对其进行简单复习。
Mvvm拆解:
M:Model:模型,对应data中的数据;
View:模板,页面结构,可以理解为用户界面,是由真实的Dom结构构成;
VM:视图模型,对应的是Vue的实例对象。
可以根据下面这张图片进行理解:
其中,Model中的数据通过Vm传递给View,在页面中可以反映出name的值为‘巧克力小猫猿’;同样,如果改变了View中name的值(通过input的value),Model中的数据也会发生改变。
1.2 什么是响应式
响应式就是在数据变化时可以被检测并对这种变化做出响应的机制响应式,简单说就是用户更改数据(Data)时,视图可以自动刷新,页面UI能够响应数据变化。
结合Mvvm,如果我们要实现响应式,其实就是实现Model到View的同步改变,当数据发生变化时,视图也发生变化。
二, Vue2中响应式的应用
Vue2中用到响应式的地方很多,用法简单粗暴,比如模板语法。只要data中存在数据,就可以通过模板语法进行使用:
<template>
<div>
我是{{ name }}
</div>
</template>
<script>
export default {
data() {
return {
name: '巧克力小猫猿'
}
}
}
</script>
页面中即可显示data中的数据:
三,Vue2响应式的原理及实现
3.1 数据代理
数据代理的定义:通过一个对象代理对另一个对象中的属性的操作(读/写),就是数据代理。
Vue2的响应式原理离不开数据代理。数据代理是什么意思?这里用一个例子来解释。如下,person是一个对象:
let person = {
name: '张三',
age: 18
}
我们可以试着在浏览器控制台来读取数据,修改数据。但是,在vue2中,是vm来管理这些数据。也就是说,我们不直接操纵person,而是通过vm(Vue的实例对象)来管理person中的数据。这种情况就叫做数据代理。
3.2 数据代理的原理
说到数据代理,我们需要了解Object.defineProperty。通过它我们可是实现一个对象去管理另一个对象中的数据。
这里介绍一下它的语法:
Objtec.defineProperty(参数一, 参数二, 参数三)
参数一:需要添加或设置属性的对象
参数二:需要添加或设置属性的属性名
参数三:配置对象
我们来看一段实用的代码:
<body>
<script>
let person = {
name: '张三',
age: 18
}
//模拟vue2中实现响应式
let p = {}
Object.defineProperty(p, 'name', {
get() {
//有人读取name时调用
return person.name
},
set(value) {
//有人修改name时调用
console.log('有人修改了name属性,我发现了,我要去更新界面')
person.name = value
}
})
</script>
</body>
在这一段代码中,我们定义了一个空对象p,并用p管理person中的name。在这段代码中,get是指读取,会在读取参数二name时调用,set是指更改,会在更改参数二name时被调用。可以看出,我们在用新建立的对象p去读取person中的name,或者更改person中的name时,会分别调用get和set。
这样,就做到了用对象p去修改person中的内容。
在vue2中,也是通过这样的原理,用Vue的实例对象vm来管理data中的数据。
3.3 数据劫持
在弄懂上述的原理后,我来讲解一个概念:数据劫持。以上面的例子为主来说明,如果name被读取 ,会被p监测到并调用get,如果name被修改,会被p检测到并调用set。这样在中间劫取数据并进行操作的情况就叫做数据劫持。
现在我们来看一下数据劫持的概念:指的是在访问或者修改对象的某个属性时,通过一段代码拦截这个行为,进行额外的操作或者修改返回结果。
四,Vue3中响应式的应用
3.1 ref与reactive
在Vue3中,如果要实现响应式,离不开ref和reactive。Vue3中的数据书写不像Vue2一样使用data,而是存储在setup函数中。setup函数中可以放置普通类型的数据,也可以放置对象类型的数据。最后需要暴露的数据被setup函数return出来即可。
但是在数据的书写中,如果要实现响应式,就需要ref和reactive。如下代码,这是一个setup函数。其中,==普通类型的数据用ref函数,对象类型的数据用reactive函数。==这也是Vue3响应式的基本用法。
这里注意,ref和reactive都需要提前引入。
import { ref, reactive } from 'vue'
setup() {
//数据
var name = ref('zxd')
var age = ref(18)
// var sex = '女'
var sex = ref('女');
//对象类型数据
let job =reactive({
type: "前端工程师",
salary: '30k',
a: {
b: {
c:666
}
}
})
return {name, age, sex, job}
}
3.2 ref函数的使用讲解
我们先用ref包裹一个数据,并看一下,该数据被ref包裹后是什么,请看如下代码:
<template>
<h1>姓名{{ name }}</h1>
<button @click="look">点击查看name</button>
</template>
<script>
import { ref, reactive } from 'vue'
export default {
setup() {
let name = ref('巧克力小猫猿')
function look() {
console.log(name)
}
return {
name, look
}
}
}
</script>在这里插入图片描述
点击查看name:
可以看出name是一个RefImpt对象。这里解释下RefImpt是什么 :RefImpt全程,reference(引用)implement(实现)对象,所以其称之为引用实现对象的实例对象,简称引用对象。
现在提一个需求,我们需要修改name的value改成巧克力,如下代码如果我们直接更改name:
<template>
<h1>姓名{{ name }}</h1>
<button @click="look">点击查看name</button>
<button @click="change">修改name的名字</button>
</template>
<script>
import { ref, reactive } from 'vue'
export default {
setup() {
let name = ref('巧克力小猫猿')
function look() {
console.log(name)
}
function change() {
name = '小猫'
console.log(name)
}
return {
name, look, change
}
}
}
</script>
运行结果是name被改变但没有响应式:
我们来试一下name.value进行修改,则可以成功被修改:
所以,在使用ref的时候,如果是在模板语法中,不用写value,但是在JavaScript中操作,value不可以省略。
3.3 为什么要出现reactive及其用法
为什么对象类型要使用reactive?这里我们可以先用ref试一下,看看能不能实现功能。
如下代码:
<template>
<h1>姓名{{ name }}</h1>
<h1>工作{{ job.type }}</h1>
<h1>薪水{{ job.salary }}</h1>
<button @click="look">点击查看name</button>
<button @click="change">修改name的名字</button>
<button @click="look2">查看对象</button>
</template>
<script>
import { ref, reactive } from 'vue'
export default {
setup() {
let name = ref('巧克力小猫猿')
let job = ref({
type: '前端工程师',
salary: '60k'
})
function look() {
console.log(name)
}
function change() {
name.value = '小猫'
console.log(name)
}
function look2() {
console.log(job)
}
return {
name, look, change, job, look2
}
}
}
</script>
我们添加了一个job对象并打印:
可以看出依旧是一个引用对象,到目前为止都没有出现什么问题。比较明显的区别是,Value处显示的是一个Proxy,我们目前不知道是什么,但是可以通过打印来观察:
可以看到,Proxy也是一个对象:
那么,如果我们要修改对象中的内容,可以通过job.value.type,job.value.salary来修改:
可以修改,但是非常麻烦,每一次都要写一个value。那么我们如何简化,就出现了reactive。reactive和ref用法基本相同:
<template>
<h1>姓名{{ name }}</h1>
<h1>工作{{ job.type }}</h1>
<h1>薪水{{ job.salary }}</h1>
<button @click="look">点击查看name</button>
<button @click="change">修改name的名字</button>
<button @click="look2">查看对象</button>
<button @click="change2">修改薪水</button>
</template>
<script>
import { ref, reactive } from 'vue'
export default {
setup() {
let name = ref('巧克力小猫猿')
let job = reactive({
type: '前端工程师',
salary: '60k'
})
function look() {
console.log(name)
}
function change() {
name.value = '小猫'
console.log(name)
}
function look2() {
console.log(job)
console.log(job.value)
}
function change2() {
job.salary = '90k'
}
return {
name, look, change, job, look2, change2
}
}
}
</script>
但是我们不需要写value就可以更改成功:
另外,reactive相比于ref有一个好处,就是reactive响应数据是深层次的。
比如这样一个对象:
我们需要修改c:
<template>
<h1>姓名{{ name }}</h1>
<h1>工作{{ job.type }}</h1>
<h1>薪水{{ job.salary }}</h1>
<button @click="look">点击查看name</button>
<button @click="change">修改name的名字</button>
<button @click="look2">查看对象</button>
<button @click="change2">修改薪水</button>
<button @click="changec">修改c</button>
{{ job.a.b.c }}
</template>
<script>
import { ref, reactive } from 'vue'
export default {
setup() {
let name = ref('巧克力小猫猿')
let job = reactive({
type: '前端工程师',
salary: '60k',
a: {
b: {
c: 1
}
}
})
function look() {
console.log(name)
}
function change() {
name.value = '小猫'
console.log(name)
}
function look2() {
console.log(job)
console.log(job.value)
}
function change2() {
job.salary = '90k'
}
function changec() {
job.a.b.c = 2
}
return {
name, look, change, job, look2, change2, changec
}
}
}
</script>
非常的简便,直接job.a.b.c即可:
五, Vue3响应式的原理及实现
5.1 底层原理
Vue3响应式的底层依旧是通过数据代理和数据劫持来实现vm对数据的管理。但是方式与Vue2有所不同。我们先看下基本结构,依旧是一个person对象:
<!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>
let person = {
name: '张三',
age: 18
}
</script>
</body>
</html>
这里需要给读者介绍以下Proxy函数。Proxy函数是window身上内置的,使用方式:
const p = new Proxy(person, {})
其中,p是代理对象,Proxy内部,person是源数据(需要管理的数据),后面的则是数据劫持中捕获数据的修改。
请看以下使用:
const p = new Proxy(person, {
get(target, propName) {
console.log(`有人读取了p身上的${propName}属性`)
return target[propName]
},
set(target, propName, value) {
console.log(`有人修改了p身上${propName}`)
target[propName] = value
},
deleteProperty(target, propName) {
return delete target[propName]
}
});
这里,依旧有get,set,以及山粗deleteProperty。通过p对源数据person进行管理。这里要注意,target是指源数据,而propName是指读的谁。
至于这里的return为什么这么写,这是Es6的写法,因为propName是target中的一个变量,而不是字符串。这里的propName和target是类似形参的,而不是实参。
5.2 window上内置对象Reflect
Reflect对象可以从对象中读数据。例如Reflect.get(‘obj’, ‘a’)意思是从obj中读a属性,那么我们的return就无需刚刚那样写了,可以都使用Reflect:
const p = new Proxy(person, {
get(target, propName) {
console.log(`有人读取了p身上的${propName}属性`)
// return target[propName]
return Reflect.get(target, propName)
},
set(target, propName, value) {
console.log(`有人修改了p身上${propName}`)
// target[propName] = value
return Reflect.set(target, proName, value)
},
deleteProperty(target, propName) {
// return delete target[propName]
return Reflect.deleteProperty(target.propName)
}
});
以上就是Vue3的响应式原理。
5.3 Vue3响应式原理的总结
1.通过Proxy进行代理,拦截对象中任意属性的变化,包括属性的读写,添加,删除。
2.通过Reflect反射对源对象进行操作。
reactive与ref的对比:
1.原理上,ref通过Vue2中的响应式原理get与set实现响应式;reactive通过使用Proxy来实现响应式,并通过Reflect操作源对象内部数据。
2.使用角度:ref定义的数据操作需要value,读取无需value,而reactive定义的数据均不需要value。
后记
本篇博客讲解了Vue2与Vue3响应式的用法,原理与对比。如果有什么问题,期望大家批评指正。
最后,欢迎关注,希望给读者带来更好的文章!