目录
- 1、两者的区别
- 底层实现
- 响应式引用与响应式对象
- 2、用法
- 3、vue3中声明的数组/对象
- 3.1 通过reactive 声明的Array/Object,给它重新分配一个全新的对象时,会出错、或失去响应式效果
-
3.2 解决方案
- 4、cosnt 说明
- 5、Proxy 与 defineProperty
- ref 浅层响应式问题的解决方案
- 总结
- `ref`
- `reactive`
1、两者的区别
ref
:通常用于定义一个响应式引用,例如Number、String、Boolean、Object、Array等- 可以用于定义一个基本数据类型、或者引用数据类型的响应式引用,返回的是一个带有
.value
属性的对象,但它的.value
是一个 Proxy对象,这使得 Vue 也能够正确追踪和响应这些引用的变化 - 更简单直观,通过
.value
来访问和修改值,代码量少,易于理解 - 如果是基本类型(单一数据值),
ref
也会将它转换成响应式数据,便于监听数据变化
- 可以用于定义一个基本数据类型、或者引用数据类型的响应式引用,返回的是一个带有
reactive
:用于定义一个的响应式对象,例如Array、Object等;- 仅用于定义一个引用类型的响应式对象,返回的是深度响应的Proxy对象,对象的任何数据发生变化时(增删改)都会被监测到
- 必须是不需要重新分配一个全新的对象的对象
若重新赋值,会报错或造成响应式丢失,建议使用ref
,详细解释看下方的 vue3中声明数组
无论是ref、reactive最终返回的值都可以监听追踪到值的变化
reactive仅用于定义引用数据类型,这是因为底层逻辑用的是
Proxy
实现的,Proxy
不适用 基本数据类型
尽量不要用reactive
定义基本类型,有警告错误
简单解释一下“重新分配一个全新的对象”,用
ref
更合适的原因:
ref
返回的是响应式引用,在修改值时,用的是.value
,而不是直接修改整个ref
而reactive
返回的是响应式对象,在修改值时,是直接赋值(state={}),等同于改掉了整个对象
底层实现
ref
会创建一个含有.value
属性的对象,Vue 会拦截对 .value
的访问和修改操作,确保在 .value
变化时触发响应式系统的更新。
reactive
主要用于处理复杂对象和数组的响应性。它返回的是一个代理对象,通过 Proxy
拦截对该对象属性的访问和修改,从而实现响应式。
响应式引用与响应式对象
响应式引用
:
- 引用语义:
ref
返回一个对象,对象中的.value
属性是响应式的,我们通过引用.value
来读取和修改值; - 原始值的响应性:读取和修改原始值,也就是读取、修改、重新赋值
.value
,这并不会改变ref
对象本身的引用,因此响应性不会丢失; - 在处理值时,
ref
会对其进行进行浅层包装,使得可以追踪对象引用的变化,但不会深层追踪对象内部属性的变化。所以通过ref
声明的 层级太多的对象,可能不会深层追踪对象内部属性的变化,也就是说监测不到所有内部属性的变化。【目前笔者还未测试出来】
响应式对象
:
reactive
返回一个proxy
响应式代理对象,只能修改它的属性,不能重新赋值;- 可以追踪其内部所有属性的变化,当属性值被修改时,视图也会自动更新
2、用法
- 在
setup()
中使用<template><div>{{ count }} </div><div>{{ state.age}} </div><button @click="changeCount">修改count</button><button @click="changeAge">修改Age</button> </template> <script>import { ref, reactive } from 'vue';export default {setup() {const count = ref(0);const state = reactive({name: 'Alice',age: 30});function changeCount() {count.value++;}function changeAge() {state.age++;}return {count,state,changeCount, // 将方法暴露出去changeAge // 将方法暴露出去};}}; </script>
- 在
<script setup>
中使用<template><div>{{ count }} </div><div>{{ state.age}} </div><button @click="changeCount">修改count</button><button @click="changeAge">修改Age</button> </template> <script setup> import { ref, reactive } from 'vue'; const count = ref(0); const state = reactive({name: 'Alice',age: 30 }) function changeCount() {count.value++; }function changeAge() {state.age++;} </script>
3、vue3中声明的数组/对象
ref
,这是为了避免 对 reactive
定义的值进行重新分配一个全新的对象时,导致的响应式丢失问题。
当然,如果不是重新分配一个全新的对象,推荐用 reactive
,具体讲解请看 3.2 解决方案
案例如下:
<template><div>refList</div><div v-for="(v, i) in refList" :key="i">{{ v }}</div><button @click="changeRef">修改ref</button><div>reactive</div><div v-for="(v, i) in reactiveList" :key="i">{{ v }}</div><button @click="changeReactive">修改reactive</button>
</template><script setup>
import { ref, reactive } from 'vue'
const refList = ref([])
const reactiveList = reactive([])
function changeRef() {// 改变 refList
}function changeReactive() {// 改变 reactiveList
}
</script>
3.1 通过reactive 声明的Array/Object,给它重新分配一个全新的对象时,会出错、或失去响应式效果
- 用
const
声明的Array
,重新赋值时会报错【提示它是一个只读常量】,这等同于给它重新分配一个全新的对象,const
声明的Object
也一样
const reactiveList = reactive([1, 2, 3])
function changeReactive() {reactiveList = ['01', 1, 2] // 'reactiveList' is constant. eslint[...]
}
//const reactiveList = reactive({ '0': 1,'1': 2,'2': 3 })
// function changeReactive() {
// reactiveList = { '0': '01','1': 2,'2': 3 } // 'reactiveList' is constant. eslint[...]
// }
- 用
let
声明的Array
,重新赋值时可以赋值成功,但它失去了响应式效果,用let
声明的Object
也一样
let reactiveList = reactive([1, 2, 3])
function changeReactive() {reactiveList = ['01', 2, 3]console.log(reactiveList) // 输出结果是['01', 2, 3],但页面渲染还是[1, 2, 3]
}
// let reactiveList = reactive({ '0': 1,'1': 2,'2': 3 })
// function changeReactive() {
// reactiveList = { '0': '01','1': 2,'2': 3 }
// console.log(reactiveList) // 输出结果是{ '0': '01','1': 2,'2': 3 },但页面渲染还是{ '0': 1,'1': 2,'2': 3 }
// }
3.2 解决方案
ref
则是创建一个包含原始值的响应式引用(ref)。当 ref 的值改变时,会触发依赖更新。reactive
创建一个深度响应式对象,对 对象的所有嵌套属性进行响应式处理,说简单点,就是只对它进行修改,不重新赋值。
方法一:用 ref
声明 Array,重新分配一个新对象时,不会失去响应,Object也一样
const refList = ref([1, 2, 3])
function changeReactive() {refList.value = ['01', 2, 3]console.log(refList.value) // 输出结果是['01', 2, 3],页面渲染也是['01', 2, 3]
}const refList = ref({ '0': 1,'1': 2,'2': 3 })
function changeRef() {refList.value = { '0': '01','1': 2,'2': 3 }console.log(refList.value) // 输出结果是{ '0': '01','1': 2,'2': 3 },页面渲染也是{ '0': '01','1': 2,'2': 3 }
}
方法二:用 reactive
声明的Array,修改时使用Array.push、splice等方法,object同理
const reactiveList = reactive([1, 2, 3])
function changeReactive() {reactiveList.push(4) // 输出结果是['01', 2, 3, 4],页面渲染也是[1, 2, 3, 4]console.log(reactiveList)
}
4、cosnt 说明
在 Vue 2 中,使用 const 声明的变量确实是 常量
,因为 Vue 2 的响应式系统是基于 Object.defineProperty 实现的,无法追踪 const 变量的重新赋值。
const
声明的变量不可重新赋值,但其引用的对象是可变的,想要改变它,只能改变它内部的属性
let
声明的变量可以重新赋值
但在 Vue 3 中,采用了基于 Proxy 的新响应式系统,const 声明的变量依然可以是响应式的。
在vue3的 setup
函数中,const
声明的变量被称之为 响应式引用 或 响应式对象
所以在vue3中,用const
声明一个reactive
对象时,想要改变它,只能改变它内部的属性
如果用 let
声明,虽然可以 对整个对象重新赋值,但这就等同于 给它赋值了一个新的引用,因此之前绑定的响应关系失效了
总结
reactive
创建响应式对象:修改其属性时是响应式的;const
声明的变量:不可重新赋值,确保引用不变,从而保持响应性;let
声明的变量:可以重新赋值,但重新赋值为新对象,就会失去响应性;- 用
const
声明的reactive
响应式对象,是一个固定引用的响应式对象,使用const
主要就是为了防止重新赋值,而失去响应性。
5、Proxy 与 defineProperty
reactive方法内部是利用ES6的Proxy API来实现的,这里与Vue2中的defineProperty方法有本质的区别。
- defineProperty只能单一地监听已有属性的修改或者变化,无法检测到对象属性的新增或删除,而Proxy可以轻松实现;
- defineProperty无法监听属性值是数组类型的变化,而Proxy可以轻松实现。
ref 浅层响应式问题的解决方案
ref
在处理值时,ref 会对其进行进行浅层包装,使得可以追踪对象引用的变化,但层级太多的对象,可能监测不到所有内部属性的变化。
方法一:结合 toRefs 将 reactive 对象转换为 ref
import { reactive, toRefs } from 'vue';
const state = reactive({nested: {count: 0}
});// 将 reactive 对象的属性转换为 ref
const { nested } = toRefs(state);
// 修改嵌套属性会触发响应式更新
nested.value.count = 1; // 响应式更新
方法二:用this.$forceUpdate()强制刷新
import { ref } from 'vue';
const state = ref({nested: {count: 0}
});// 修改嵌套属性会触发响应式更新
nested.value.count = 1; // 响应式更新
this.$forceUpdate();
方法三:手动修改、触发响应
import { ref } from 'vue';const state = ref({nested: {count: 0}
});function forceUpdate() {state.value = { ...state.value };
}state.value.nested.count = 1; // 这不会触发响应式更新
forceUpdate(); // 这会触发响应式更新
总结
ref
- 用于定义一个响应式引用,可以是基本数据类型、引用数据类型,
ref
会返回一个带有.value
属性的对象,而这个.value
就是proxy响应式代理对象; - 由于
.value
就是proxy响应式代理对象,所以在读取、修改、重新赋值时针对.value
,这并不会改变ref
对象本身的引用,因此响应性不会丢失; - 在定义
Array
时,更推荐使用ref
,这是因为不能对reactive
对象重新定义一个全新的引用 - 在处理值时,ref 会对其进行进行浅层包装,使得可以追踪对象引用的变化,但层级太多的对象,可能监测不到所有内部属性的变化。可以用(1)this.$forceUpdate()强制刷新;(2)ref结合reactive;(3)手动修改、触发响应
reactive
- 仅用于定义一个固定引用的响应式对象,必须是引用数据类型,
reactive
返回的是深度响应的proxy对象,对象的所有属性发生变化时,都会被监测到; - 必须是 不需要重新分配一个全新的引用的对象,这是因为修改、重新赋值都是直接对
reactive
,如果重新赋值,等同于重新赋值了一个全新的引用,那么之前绑定的响应关系就丢了;
备注:
如有理解错误的观点,请在评论区留言,接受批评和指导