Vue3 – Ref 与 Reactive 、readonly响应式函数
简介
Vue3 中所提供的响应式方法,不单单使用 set 和 get 方法了,提供了两种创建响应式函数的方式 ref 与 reactive 两种。
ref 函数
ref 函数是 reference 的简称,中文名为“引用”的意思,被 ref 包装加工过的基础数据类型,都将变为 RefImpl 对象数据。
// 需要引用 { ref } 暴露
import {ref} from "vue";
export default {
name: 'App',
setup() {
/**
* 定义 ref 响应式函数
* @type {Ref<UnwrapRef<string>>}
*/
let name = ref("张三");
let age = ref(18);
let job = ref({
type: "前端工程师",
salary: "30k"
})
/**
* 修改 ref 对象中的数据,并实现响应式处理
*/
function changeData() {
name.value = "李四";
age.value = 20;
// 修改 ref 包装了的对象的方式
job.value.type = "Java工程师";
job.value.salary = "40k";
}
return {
// 返回自定义数据
name, age,
changeData
}
}
}
原理:ref 依然使用 Vue2 上的Object.defineProperty 的方式定义 get set 函数,实现响应式,但对于对象类型的数据,Vue3 使用了 ES6 中的新特性 Proxy 方式。
注意:
1.如果需要在内部方法体内修改 ref 对象中的数据时,需要使用 .value 取得数据。
2.在 template 模板中使用 ref 对象,Vue3 已自动帮我们处理了 .value 访问的问题,所以不再需要手动在 template 中加上 .value 访问数据
3.ref 只使用 Object.defineProperty 的方式包装了基础数据类型,对于使用 ref 包装的对象数据,ref将“求助”Proxy 对对象进行响应式包装,并未使用 Object.defineProperty 的方式包装对象内的数据 。因此,如果要修改 ref 包装了的对象中的成员数据时,只要使用 .value.xxx = xx 就可以。
shallowRef 函数
shallowRef 是对于 ref 函数的浅响应式,对于基础数据类型而言,shallowRef 函数与 ref 函数功能是一致的。
但是当 ref 包裹的是一个对象时,ref 会求助于 reactive 对对象创建Proxy实现响应式。
但是 shallowRef 包裹的是一个对象时,shallowRef 并不会求助于 reactive. 而是直接认为该对象就是 Object 类型,相对而言,shallowRef 包裹的对象里面的数据,是没有响应式功能的。但如果当整个对象发生改变时(如对象内存地址发生改变),shallowRef 会作响应式处理。
reactive 函数
reactive 函数是 Vue3 中专门用来包装 对象 类型的数据,且仅只支持 对象 类型的数据,使用 ES6 新特性 Proxy 对对象数据进行封装处理。
import {reactive} from "vue"
export default {
name: 'App',
setup() {
/**
* 定义 ref 响应式函数
* @type {Ref<UnwrapRef<string>>}
*/
let name = ref("张三");
let age = ref(18);
/**
* 定义 reactive 响应式函数
* @type {UnwrapNestedRefs<{type: string, salary: string}>}
*/
let job = reactive({
type: "前端工程师",
salary: "30k"
});
// reactive 也支持使用数组
let hobby = reactive(["a","b","c"])
/**
* 修改 ref 对象中的数据,并实现响应式处理
*/
function changeData() {
name.value = "李四";
age.value = 20;
// 修改 reacitve 包装了的对象的方式
job.type = "Java工程师";
job.salary = "40k";
// 直接通过数组下标修改数组中的成员数据
hobby[0] = "d";
}
return {
// 返回自定义数据
name, age,
changeData
}
}
}
注意:reactive 不能包装基础数据类型,包装基础数据类型应使用 ref.而如果包装的是对象类型的数据,应当使用 reactive,而不使用 ref.因为使用reactive包装的数据,在内部方法中修改可以省去使用 .value 的操作。
且 reactive 使用了深度 Proxy,即使嵌套结构再深的对象成员数据,依然可以被响应。
shallowReactive
shallowReactive 是针对 reactive 的浅响应式处理,当 reactive 对一个对象进行响应式加工时,会对对象中的所有嵌套对象也一并加工成响应式。
但 shallowReactive 仅对对象的第一级成员变量进行响应式处理,对于对象里的嵌套对象,shallowReactive 不会作加工处理,因此如果使用shallowReactive包裹的多层次多象时,其内部的对象数据发生变化时,不会产生响应式处理。
当对象的第一级成员数据发生变化时,才会被响应。
解决Vue2中的问题
增删响应式数据问题
对于Vue2中的增删数据而言,不能通过直接增加或删除进行操作,因为这样Vue2不能识别到数据的变动,也不存在 get set 方法,如下
data(){
return {
person:{
name: "张三"
age:18
}
}
},
methods:{
change(){
// 增加 person 中的 sex 属性
this.person.sex = "男"
// 删除 person 中的 name 属性
delete this.person.name;
}
}
以上方法在Vue2 中起不到响应式的作用,因为 Vue2 使用的是 Object.defineProperty() 方法对数据进行监控,但是对于删除和增加,Vue2 并不能通过这种方式进行,这是因为 Object.defineProperty() 不支持 delete 和 增加的方法。
需要使用 Vue2 提供的 this.$set(Vue.set) 方法增加数据,和使用 this.$delete(Vue.delete) 方法删除数据,如下
data(){
return {
person:{
name: "张三"
age:18
}
}
},
methods:{
change(){
// 增加 person 中的 sex 属性
this.$set(this.person,'sex','男');
// 删除 person 中的 name 属性
this.$delete(this.person,'name');
}
}
而Vue3中,则可以直接通过增删数据自动改变
数组成员修改问题
对于Vue2 的数据处理而言,也不能通过直接的数组下标修改数据,如下
data(){
return {
person:{
name: "张三"
age:18,
hobby:["学习","睡觉"]
}
}
},
methods:{
change(){
// 修改数组中第一个成员的数据
this.person.hobby[0] = "吃饭"
}
}
以上代码在Vue2中并不能起到作用,原因也是 Object.defineProperty() 不支持 数组通过这种方式进行修改。
而是通过使用 Vue2 提供的 splice 方法进行操作:
data(){
return {
person:{
name: "张三"
age:18,
hobby:["学习","睡觉"]
}
}
},
methods:{
change(){
// 修改数组中第一个成员的数据
this.person.hobby.splice(0,1,'吃饭')
}
}
而Vue3中,则可以直接通过数组下标的方式改变成员数据。
ref 与 reactive 的区别
从定义数据的角度对比:
ref 用来定义 : 基本数据类型
reactive 用来定义:对象(或数组)类型的数据
备注:ref 也可以用来定义对象(或数组)类型数据,它内部会自动通过 reactive 转为代理对象
从原理角度对比:
ref 通过 Object.defineProperty() 的 get 和 set 来实现响应式(数据劫持)
reactive 通过使用 Proxy 来实现响应式(数据劫持),并通过 Reflect 操作源对象内部的数据。
从使用角度对比:
ref 定义的数据:操作数据需要 .value, 读取数据时模板中直接读取不需要 .value.
reactive 定义的数据:操作数据与读取数据:均不需要 .value
readonly
readonly 故名思义就是,把一个响应式数据作为一个只读的数据,当出现修改该响应式数据时,将被阻止,readonly是深只读,也就是说,响应式数据中的所有成员(无论嵌套多少层)都会被阻止改写。
代码如下:
import {reactive,toRefs,readonly} from "vue"
export default {
name: 'App',
setup() {
let person = reactive({
name: "张三",
age: 18,
job: {
j1: {
salary: 20
}
}
});
// 创建只读响应式数据
person = readonly(person);
return {
/**
* 相当于返回以下别名,但仅分离第一级对象成员
* 如 job 内的 j1 不会再被分离
* name: person.name
* age: person.age
* job: person.job
*/
...toRefs(person)
}
}
}
shallowReadonly
shallowReadonly 是 readonly 的浅只读函数,把一个响应式数据作为一个只读的数据,当出现修改该响应式数据时,将被阻止,shallowReadonly 是浅只读,也就是说,只会阻止响应式数据中的第一级成员数据不被改写,嵌套对象成员数据可以被改写。
Vue3 中的响应数据原理
Vue2 中使用 Object.defineProperty() 函数对对象中的每一个成员数据进行单独监控,Vue3中,对于对象而言,Vue3 使用了ES6 全新的特性,Proxy.Proxy 可以直接对整个对象数据中的所有成员进行统一监控,只要对象中有任何变动,都会引起Proxy的触发。其具体基础代码如下:
<script>
let person = {
name: "张三",
age: 18
}
let p = new Proxy({
/**
* 对 person 进行 Proxy 代理包装,
* 包装后 person 中的任意数据被访问,都会引起 Proxy get 的触发
* @param target 源数据,这里指 person
* @param propName 被变动的属性名
* @returns {*}
*/
get(target, propName) {
console.log(`有人获取${target}对象的${propName}属性`);
return Reflect.get(target, propName);
},
/**
* 对 person 进行 Proxy 代理包装,
* 包装后 person 中的任意数据发生变化(包括数据的修改和属性的增加),都会引起 Proxy 的触发
* @param target 源数据,这里指 person
* @param propName 被变动的属性名,或增加的属性名
* @param value 变动后的新数据,或增加的属性的值
*/
set(target, propName, value) {
console.log(`有人修改了${target}对象的${propName}属性,其值是${value}`);
Reflect.set(target, propName, value);
},
/**
* 对 person 进行 Proxy 代理包装,
* 包装后 person 中的任意数据发生删除时,都会引起 Proxy 的deleteProperty 触发
* @param target 源数据,这里指 person
* @param propName 被删除的属性名
* @returns {boolean} 需要返回一个boolean值,告诉是否删除成功
*/
deleteProperty(target, propName) {
console.log(`有人删除了${target}对象的${propName}属性`)
return Reflect.deleteProperty(target, propName);
}
});
</script>
通过使用 Reflect 反射对对象中的属性进行增删改查
共有 0 条评论