-
Notifications
You must be signed in to change notification settings - Fork 0
Description
Vue3 初探 ref 与 reactive 响应式原理
参考文章:一文搞懂Vue3的响应式函数ref和reactive
✅ 作用:声明响应式数据
Vue3中通过ref()
和reactive()
函数来声明响应式数据。
ref()
可以声明任意类型的响应式数据。reactive()
只能声明对象(数组)类型的响应式数据。
let tom: Person = {
age: 30,
name: "cat"
}
//1。通过ref声明响应式数据
const ref1 = ref<number>(1);//基础类型数据
const ref2 = ref<Person>(tom); //对象/数组类型数据
//2. 通过reactive声明相适应数据
const number = reactive(1);//报错不能声明基本类型的数据,只能传入一个对象。
const reactive1 = reactive(tom);//可以
✅ reactive()
函数
reactive()
函数内部通过 New Proxy()
的方式来对数据进行代理。从而实现响应式。由于Proxy的限制,只能用来创建对象类型的响应式数据。
通过reactive()
函数,会得到一个代理对象(也叫响应式对象),只有通过代理对象操作属性,才是响应式的,通过原对象操作属性是不会响应。
let tom: Person = {
age: 30,
name: "cat"
}
const reactive1 = reactive(tom); //返回代理对象。(也叫响应式对象)
reactive1.name = "cat";//操作代理对象,响应式。
tom.name = "mi"; //操作原对象,不是响应式。
- ❇️ reactive 的单例模式
reactive()
函数的设计是单例模式:对同一个对象多次调用reactive()
函数,返回的都是同一个代理对象。对一个代理对象调用reactive()
函数,总会返回代理对象自身。
这个规则对嵌套对象也适用。依靠深层响应性,响应式对象内的嵌套对象依然是代理对象。
let tom: Person = {
age: 30,
name: "cat"
}
const reactive1 = reactive(tom);
const reactive2 = reactive(tom);
const reactive3 = reactive(reactive2);
console.log(reactive1 == reactive2); //true
console.log(reactive3 == reactive2); //true
console.log(reactive3 == reactive1); //true
- ❇️ reactive的深层代理
reactive()
是深层次的代理,响应式对象内的嵌套对象依然是代理对象。并且也是单例模式。
Proxy
是浅代理,对于嵌套的对象的不代理的,reactive()
之所以深层次代理是因为它内部对嵌套对象做了递归代理处理。
let tom: Person = {
age: 30,
name: "cat",
sex: {
ho: "02",
sex: "男"
},
}
const reactive1 = reactive(tom);
console.log(reactive1.sex); //通过打印即可验证,打印出来的是一个响应式对象
- ❇️
reactive()
函数的局限性
-
只能处理对象类型:
- 由于
Proxy
的限制,reactive()
函数只对对象类型的数据有效,对于基本数据类型的代理是无效的。
- 由于
-
不能更改响应式对象引用:
- 因为 Vue 的响应式系统是通过 属性 访问进行追踪的,因此我们必须始终保持对该
响应式对象的相同引用
。(Vue官网解释) - 意思是:将响应式对象的变量赋值给另一个对象,丢失响应式。(修改嵌套的响应式对象里面嵌套对象引用可以,因为是深层代理。)
- (通过ref函数生成的可以,)后面介绍。
let tom: Person = { age: 30, name: "cat" } let reactive1 = reactive(tom); reactive1 = { age: 0, name: "cat" }//将响应式对象的变量赋值给另一个对象,丢失响应式 //修改嵌套响应式对象里面嵌套的对象引用可以,因为是深层代理。 let tom: Person = { age: 30, name: "tom", sex: { ho: "02", sex: "男" }, } let reactive1 = reactive(tom); reactive1.sex = { ho: "03", sex: "女" }
- 因为 Vue 的响应式系统是通过 属性 访问进行追踪的,因此我们必须始终保持对该
-
属性赋值失去响应式:
-
当我们将响应式对象的
属性
赋值给另一个变量、使用解构赋值、将属性传入一个函数中,我们会失去响应性。 -
只对基本数据类型有限制。如果属性的类型是对象类型,则不受限制。
-
原因:
基本数据类型是值传递,而对象类型是引用传递。
- 因为值传递的本意是复制值,就是简单的复制一个值给另一个变量。
- 而对象类型的数据不会失去响应式是因为:1.对象是引用传递,复制的是对象的引用。2.在进行代理的时候是深层次的代理,因此该对象自身就是响应式对象。
let tom: Person = { age: 30, name: "cat" } let reactive1 = reactive(tom); let ng: string = reactive1.name; // 将属性赋值给另一个变量,ng变量不是响应式。 let {age, name} = reactive1; // 使用解构赋值,得到的属性不是响应式。 setName(reactive1.name);//将属性传入函数中,该属性不是响应式 function setName(name: string) { name = "cat02" } //属性是一个对象类型。 let tom: Person = { age: 30, name: "cat", sex: { ho: "02", sex: "男" }, } let reactive1 = reactive(tom); setName(reactive1.sex) function setName(sex: Sex) { sex.sex = "女";//可以修改,是响应式。 } let {sex: sex1} = reactive1; sex1.ho = "sssss";//可以修改,是响应式。
-
✅ ref()
函数
为了解决 reactive()
带来的限制,Vue 也提供了一个 ref()
方法来允许我们创建可以使用任何值类型的响应式 ref对象。
通过ref()
函数创建响应式的数据,会返回一个RefImpl
对象(也叫ref对象
),响应式数据会被封装到ref对象的.value
属性中。(.value
属性就是响应式属性)
如果是基本类型的数据,那么ref
函数就会使用Object.defineProperty()
来实现对数据的劫持。
如果是对象(数组)类型的数据,则ref函数就会调用reactive()
函数。而.value
就是代理对象
let tom: Person = {
age: 30,
name: "tom",
}
const ref1 = ref(1);//基本类型
console.log(ref1.value);
const ref2 = ref<Person>(tom);//对象类型
console.log(ref2.value); //可以看到时一个代理对象
使用ref()
函数创建响应式的数据,是可以更改对象的引用的。(通过reactive函数的是不能替换)
const person = ref<Person>({
age: 20,
name: "tom"
});
let tom: Person = {
age: 30,
name: "cat"
}
person.value = tom;//可以替换,因为当值发表改变了就会调用reactive函数。
一言以蔽之,ref()
使我们能创造一种任意值的 “引用” 并能够不丢失响应性地随意传递。这个功能非常重要,因为它经常用于将逻辑提取到 组合函数 中。(Vue官网)
🔆 reactive 和 ref的区别
reactive
只能用来创建对象类型的响应式数据,而ref
则是可以创建任意数据类型的响应式对象。reactive
创建的是proxy
的代理对象,而ref创建的则是ref对象。- 通过
reactive
创建的响应式对象是不能更换对象的引用的,而通过ref
创建响应式对象是可以替换的。 reactive
是通过Proxy来对数据进行代理的,而ref
,如果是基本数据类型,通过Object.defineProperty()
来实现对数据的劫持。如果是对象类型的数据,则是通过调用reactive
函数实现。
✅ 个人理解
- 为什么
ref()
创建的响应式数据,可以更改对象的引用,而reactive()
不可以?
答:
从本质上讲两者创建的响应式数据都是不可以被更改引用的。响应式对象被创建的时候就会被注入到响应系统内部,如果强行覆盖的话,相当于抛弃了响应式对象而去转而使用一个非响应式的对象。
ref()
创建了一个响应式对象,其主要内容都被挂载在了 value 属性上,因此修改 value 属性上的引用并没有覆盖 ref 创建的响应式对象,所以我们说 ref 是可以修改对象引用的。reactive()
创建响应对象的方式不太一样,对于一个对象而言,它会将对象内所有属性注册 (且深层递归注册) 为响应式,但是对于顶层对象本身而言,它不是响应式的。因此,我们假如覆盖这个对象,相当于把该对象内所有的响应式属性都抛弃了,也就失去了响应性。(将该对象引用赋值给其他变量也是同理,新变量获得的是非响应式对象的引用,因此也不在响应系统的服务范围内)
- 什么情况下赋值不会丢失响应?
答:
首先我们要清楚为什么会丢失响应,无非是你当前操作的变量它没有在响应系统中注册,那么什么变量算是在响应系统中注册的呢?通过 Vue 提供的 API 接口 (例如 ref / reactive 等) 生成的变量。当我们将这些变量赋值给新创建的变量时,由于新创建的变量本身是没有在响应系统中注册过的,所以它不具备响应性,但是!赋值又分为两种情况,值传递和引用传递:
- 值传递:复制值,丢失响应
- 引用传递:复制引用地址,最终调用变量时仍指向响应系统中的内存地址,保留响应
因此,当我们复制一个对象引用时 (此处包括 ref 对象类型的嵌套对象,因为 vue 对它进行了深层代理),会保留其响应性;当我们复制一个基本类型时 (例如从 ref 对象属性中抽出一个基本类型值赋给新的变量),就会丢失响应。