Vue3技术2之ref函数、reactive函数、Vue3中的响应式原理
Vue3技术2
- ref函数
-
- 处理基本类型
-
- App.vue
- 处理对象类型
-
- App.vue
- 总结:ref函数
- reactive函数
-
- 只能修改对象类型
-
- App.vue
- 完善代码
-
- App.vue
- 总结:reactive函数
- Vue3.0中的响应式原理
-
- 回顾Vue2的响应式原理
- Vue3响应式原理
-
- 模拟Vue2中实现响应式
-
- index.html
- 模拟Vue3中实现响应式—Proxy
-
- index.html
- 模拟Vue3中实现响应式—Reflect
-
- 通过Reflect.defineProperty去操作
-
- index.html
- 通过Object.defineProperty去操作
-
- index.html
- 使用Reflect改善Vue3的响应式
- 总结
-
- vue2.x的响应式
- vue3的响应式
- reactive对比ref
ref函数
处理基本类型
App.vue
<template><h1>一个人的信息</h1><h2>姓名:{{name}}</h2><h2>年龄:{{age}}</h2><button @click="changeInfo">修改人的信息</button>
</template><script>
import {ref} from 'vue'
export default {name: 'App',setup(){//数据let name=ref('张三')let age=ref(18)function changeInfo(){console.log("name,age",name,age)name.value='李四'age.value=20console.log(name,age)}//返回一个对象(常用)return{name,age,changeInfo}}
}
</script>
处理对象类型
App.vue
<template><h1>一个人的信息</h1><h2>姓名:{{name}}</h2><h2>年龄:{{age}}</h2><h2>工作:{{job.type}}</h2><h2>薪资:{{job.salary}}</h2><button @click="changeInfo">修改人的信息</button><button @click="changeObject">修改职位信息</button>
</template><script>
import {ref} from 'vue'
export default {name: 'App',setup(){//数据let name=ref('张三')let age=ref(18)let job=ref({type:'前端工程师',salary:'10k'})function changeInfo(){name.value='李四'age.value=20console.log("name",name)}function changeObject(){console.log("job",job.value)job.value.type='蓝翔技术工'job.value.salary='50k'}//返回一个对象(常用)return{name,age,job,changeInfo,changeObject}}
}
</script>
总结:ref函数
- 作用:定义一个响应式的数据
- 语法:const xxx=ref(initValue)
(1)创建一个包含响应式数据的引用对象(reference对象,简称ref对象)
(2)JS中操作数据: xxx.value
(3)模板中读取数据:不需要.value,直接:<div>{{xxx}}</div> - 备注:
(1)接收的数据可以是:基本类型、也可以是对象类型
(2)基本类型的数据:响应式依然是靠Object.defineProperty()的get与set完成的
(3)对象类型的数据:内部 “求助” 了Vue3.0中的一个新函数——reactive函数
reactive函数
只能修改对象类型
App.vue
<template><h1>一个人的信息</h1><h2>姓名:{{name}}</h2><h2>年龄:{{age}}</h2><h2>工作:{{job.type}}</h2><h2>薪资:{{job.salary}}</h2><h2>测试c:{{job.a.b.c}}</h2><h2>爱好:{{hobby}}</h2><button @click="changeInfo">修改人的信息</button><button @click="changeObject">修改职位信息</button><button @click="changeHobby">修改第一个爱好</button>
</template><script>
import {ref,reactive} from 'vue'
export default {name: 'App',setup(){//数据let name=ref('张三')let age=ref(18)let job=reactive({type:'前端工程师',salary:'10k',a:{b:{c:1}}})let hobby=reactive(['吃饭','睡觉','打游戏'])function changeInfo(){name.value='李四'age.value=20console.log("name",name)}function changeObject(){console.log("job",job)job.type="蓝翔技术工"job.salary="20k"job.a.b.c=666}function changeHobby(){hobby[0]="唱歌"}//返回一个对象(常用)return{name,age,job,hobby,changeInfo,changeObject,changeHobby}}
}
</script>
完善代码
App.vue
<template><h1>一个人的信息</h1><h2>姓名:{{person.name}}</h2><h2>年龄:{{person.age}}</h2><h2>工作:{{person.job.type}}</h2><h2>薪资:{{person.job.salary}}</h2><h2>测试c:{{person.job.a.b.c}}</h2><h2>爱好:{{person.job.hobby}}</h2><button @click="changePerson">修改人的信息</button>
</template><script>
import {reactive} from 'vue'
export default {name: 'App',setup(){//数据let person=reactive({name:"张三",age:18,job:{type:'前端工程师',salary:'10k',a:{b:{c:1}},hobby:['吃饭','唱歌','看电影']}})function changePerson(){person.name="李四"person.age=25person.job.type='UI设计师'person.job.salary='60k'person.job.a.b.c=666person.job.hobby[0]="跳舞"}//返回一个对象(常用)return{person,changePerson}}
}
</script>
总结:reactive函数
- 作用:定义一个
对象类型
的响应式数据(基本类型不要用它,要用ref函数) - 语法:const 代理对象=reactive(源对象)接收一个对象(数组),返回一个
代理对象(Proxy的实例对象,简称proxy对象)
- reactive定义的响应式数据是“深层次的”
- 内部基于ES6的Proxy实现,通过代理对象操作源对象内部数据进行操作
Vue3.0中的响应式原理
回顾Vue2的响应式原理
- 新增数据、删除数据、直接通过下标修改数组,界面不会自动更新,但是数据确实被修改了
- App.vue
<template><div class="bg"><h1>我是Vue2写的效果</h1><h2 v-show="person.name">姓名:{{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">修改第一个爱好</button></div></template><script>
export default {name: "App",data(){return{person:{name:"张三",age:25,hobby:["学习","读书","看报"]}}},methods:{addSex(){console.log(this.person.sex)this.person.sex="女"console.log(this.person.sex)console.log("---------------------")},deleteName(){console.log(this.person.name)delete this.person.nameconsole.log(this.person.name)console.log("---------------------")},changeHobby(){console.log(this.person.hobby)this.person.hobby[0]="唱歌"console.log(this.person.hobby)console.log("---------------------")}}
}
</script><style>
</style>
- 必须使用set方法或者数组原始方法修改,界面才会有响应
- App.vue
<template><div class="bg"><h1>我是Vue2写的效果</h1><h2 v-show="person.name">姓名:{{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">修改第一个爱好</button></div></template><script>
import Vue from 'vue'
export default {name: "App",data(){return{person:{name:"张三",age:25,hobby:["学习","读书","看报"]}}},methods:{addSex(){/*console.log(this.person.sex)this.person.sex="女"console.log(this.person.sex)console.log("---------------------")*///界面有响应的this.$set(this.person,'sex','女')// Vue.set(this.person,'sex','女')},deleteName(){/*console.log(this.person.name)delete this.person.nameconsole.log(this.person.name)console.log("---------------------")*///界面有响应的this.$delete(this.person,'name')// Vue.delete(this.person,'name')},changeHobby(){/*console.log(this.person.hobby)this.person.hobby[0]="唱歌"console.log(this.person.hobby)console.log("---------------------")*///界面有响应的// this.$set(this.person.hobby,0,'唱歌')this.person.hobby.splice(0,1,'唱歌')}}
}
</script><style>
</style>
Vue3响应式原理
模拟Vue2中实现响应式
index.html
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Vue3的响应式原理</title>
</head>
<body>
<script>let person={name:"张三",age:18}//模拟Vue2中实现响应式let p={}Object.defineProperty(p,'name',{configurable:true, //可配置的,可以删除属性get(){ //有人读取name时调用return person.name},set(value){ //有人修改name时调用console.log("有人修改了name属性,我发现了,我要去更新界面!")person.name=value}})Object.defineProperty(p,'age',{get(){ //有人读取age时调用return person.age},set(value){ //有人修改name时调用console.log("有人修改了age属性,我发现了,我要去更新界面!")person.age=value}})
</script>
</body>
</html>
添加与删除一个属性都捕获不到
模拟Vue3中实现响应式—Proxy
index.html
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Vue3的响应式原理</title>
</head>
<body>
<script>let person={name:"张三",age:18}//模拟Vue2中实现响应式// #region 解决vsCode中折叠后但又打开的情况/* let p={}Object.defineProperty(p,'name',{configurable:true, //可配置的,可以删除属性get(){ //有人读取name时调用return person.name},set(value){ //有人修改name时调用console.log("有人修改了name属性,我发现了,我要去更新界面!")person.name=value}})Object.defineProperty(p,'age',{get(){ //有人读取age时调用return person.age},set(value){ //有人修改name时调用console.log("有人修改了age属性,我发现了,我要去更新界面!")person.age=value}})*/// #endregion//模拟Vue3中实现响应式const p=new Proxy(person,{//有人读取p的某个属性时调用get(target,propName){console.log(`有人读取了p身上的${propName}属性`)return target[propName]},//有人修改p的某个属性、或给p追加某个属性时调用set(target,propName,value){console.log(`有人修改了p身上的${propName}属性,我要去更新界面了`)return target[propName]=value},//有人删除p的某个属性时调用deleteProperty(target, propName) {console.log(`有人删除了p身上的${propName}属性,我要去更新界面了`)return delete target[propName]}})</script>
</body>
</html>
模拟Vue3中实现响应式—Reflect
通过Reflect.defineProperty去操作
index.html
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Vue3的响应式原理</title>
</head>
<body>
<script>let person={name:"张三",age:18}//模拟Vue2中实现响应式// #region 解决vsCode中折叠后但又打开的情况/* let p={}Object.defineProperty(p,'name',{configurable:true, //可配置的,可以删除属性get(){ //有人读取name时调用return person.name},set(value){ //有人修改name时调用console.log("有人修改了name属性,我发现了,我要去更新界面!")person.name=value}})Object.defineProperty(p,'age',{get(){ //有人读取age时调用return person.age},set(value){ //有人修改name时调用console.log("有人修改了age属性,我发现了,我要去更新界面!")person.age=value}})*/// #endregion//模拟Vue3中实现响应式/*const p=new Proxy(person,{//有人读取p的某个属性时调用get(target,propName){console.log(`有人读取了p身上的${propName}属性`)return target[propName]},//有人修改p的某个属性、或给p追加某个属性时调用set(target,propName,value){console.log(`有人修改了p身上的${propName}属性,我要去更新界面了`)return target[propName]=value},//有人删除p的某个属性时调用deleteProperty(target, propName) {console.log(`有人删除了p身上的${propName}属性,我要去更新界面了`)return delete target[propName]}})*/obj={a:1,b:2}//通过Reflect.defineProperty去操作const x1=Reflect.defineProperty(obj,'c',{get(){return 3}})const x2=Reflect.defineProperty(obj,'c',{get(){return 100}})if(x2){console.log("某某某操作成功了!")}else{console.log("某某某操作失败了!")}</script>
</body>
</html>
通过Object.defineProperty去操作
index.html
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Vue3的响应式原理</title>
</head>
<body>
<script>let person={name:"张三",age:18}//模拟Vue2中实现响应式// #region 解决vsCode中折叠后但又打开的情况/* let p={}Object.defineProperty(p,'name',{configurable:true, //可配置的,可以删除属性get(){ //有人读取name时调用return person.name},set(value){ //有人修改name时调用console.log("有人修改了name属性,我发现了,我要去更新界面!")person.name=value}})Object.defineProperty(p,'age',{get(){ //有人读取age时调用return person.age},set(value){ //有人修改name时调用console.log("有人修改了age属性,我发现了,我要去更新界面!")person.age=value}})*/// #endregion//模拟Vue3中实现响应式/*const p=new Proxy(person,{//有人读取p的某个属性时调用get(target,propName){console.log(`有人读取了p身上的${propName}属性`)return target[propName]},//有人修改p的某个属性、或给p追加某个属性时调用set(target,propName,value){console.log(`有人修改了p身上的${propName}属性,我要去更新界面了`)return target[propName]=value},//有人删除p的某个属性时调用deleteProperty(target, propName) {console.log(`有人删除了p身上的${propName}属性,我要去更新界面了`)return delete target[propName]}})*/obj={a:1,b:2}//通过Object.defineProperty去操作const x1=Object.defineProperty(obj,'c',{get(){return 3}})const x2=Object.defineProperty(obj,'c',{get(){return 100}})//通过Reflect.defineProperty去操作/*const x1=Reflect.defineProperty(obj,'c',{get(){return 3}})const x2=Reflect.defineProperty(obj,'c',{get(){return 100}})if(x2){console.log("某某某操作成功了!")}else{console.log("某某某操作失败了!")}*/</script>
</body>
</html>
使用Reflect改善Vue3的响应式
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Vue3的响应式原理</title>
</head>
<body>
<script>let person={name:"张三",age:18}//模拟Vue2中实现响应式// #region 解决vsCode中折叠后但又打开的情况/* let p={}Object.defineProperty(p,'name',{configurable:true, //可配置的,可以删除属性get(){ //有人读取name时调用return person.name},set(value){ //有人修改name时调用console.log("有人修改了name属性,我发现了,我要去更新界面!")person.name=value}})Object.defineProperty(p,'age',{get(){ //有人读取age时调用return person.age},set(value){ //有人修改name时调用console.log("有人修改了age属性,我发现了,我要去更新界面!")person.age=value}})*/// #endregion//模拟Vue3中实现响应式const p=new Proxy(person,{//有人读取p的某个属性时调用get(target,propName){console.log(`有人读取了p身上的${propName}属性`)// return target[propName]return Reflect.get(target,propName)},//有人修改p的某个属性、或给p追加某个属性时调用set(target,propName,value){console.log(`有人修改了p身上的${propName}属性,我要去更新界面了`)// return target[propName]=valuereturn Reflect.set(target,propName,value)},//有人删除p的某个属性时调用deleteProperty(target, propName) {console.log(`有人删除了p身上的${propName}属性,我要去更新界面了`)// return delete target[propName]return Reflect.deleteProperty(target,propName)}})/*obj={a:1,b:2}//通过Object.defineProperty去操作const x1=Object.defineProperty(obj,'c',{get(){return 3}})const x2=Object.defineProperty(obj,'c',{get(){return 100}})
*///通过Reflect.defineProperty去操作/*const x1=Reflect.defineProperty(obj,'c',{get(){return 3}})const x2=Reflect.defineProperty(obj,'c',{get(){return 100}})if(x2){console.log("某某某操作成功了!")}else{console.log("某某某操作失败了!")}*/</script>
</body>
</html>
总结
vue2.x的响应式
- 实现原理
(1)对象类型:通过Object.defineProperty()对属性的读取、修改进行拦截(数据劫持)
(2)数组类型:通过重写更新数组的一系列方法来实现拦截。(对数组的变更方法进行了包裹)
Object.defineProperty(data,'count',{get(){},set(){}})
- 存在问题
(1)新增属性、删除属性,界面不会更新
(2)直接通过下标修改数组,界面不会自动更新
vue3的响应式
- 实现原理
(1)通过Proxy(代理):拦截对象中任意属性的变化,包括:属性值的读写、属性的添加、属性的删除等
(2)通过Reflect(反射):对源对象的属性进行操作
(3)MDN文档中描述的Proxy与Reflect:
- Proxy
- Reflect
new Proxy(data,{//拦截读取属性值get(target,prop){return Reflect.get(target,prop)},//拦截设置属性值或添加新属性set(target,prop,value){return Reflect.set(target,prop,value)},//拦截删除属性deleteProperty(target,prop){return Reflect.deleteProperty(target,prop)}
})proxy.name="tom"
reactive对比ref
- 从定义数据角度对比
(1)ref用来定义:基本类型数据
(2)reactive用来定义:对象(或数组)类型数据
(3)备注:ref也可以用来定义对象(或数组)类型数据
,它内部会自动通过reactive转为代理对象
- 从原理角度对比
(1)ref通过Object.defineProperty()
的get与set来实现响应式(数据劫持) - 从使用角度对比:
(1)ref定义的数据:操作数据需要
.value,读取数据时模板中直接读取不需要
.value
(2)reactive定义的数据:操作数据与读取数据:均不需要
.value