Vue3 中为何尤大推荐使用 Ref 而非 Reactive?
一、Ref 和 Reactive 的简介
Ref 和 Reactive 是 Vue3 中实现响应式数据的核心 API。Ref 用于包装基本数据类型,而 Reactive 用于处理对象和数组。
Ref 是一个函数,它接受一个内部值并返回一个响应式且可变的引用对象。这个引用对象有一个 .value 属性,该属性指向内部值。例如,使用 ref 创建一个基本数据类型的响应式引用:
import { ref } from 'vue';
const count = ref(100);
function changeCount() {
count.value = 200;
}
在脚本中操作 ref 创建的响应式数据时,需要通过 .value 属性获取值和修改值。而在模板中,可以直接读取,Vue3 会自动帮你加上 .value。
Reactive 是一个函数,它接受一个对象并返回该对象的响应式代理,也就是 Proxy。例如:
import { reactive } from 'vue';
const user = reactive({ name1: "小明", age1: 36 });
Reactive 的响应式是深层次的,会影响对象内部所有嵌套的属性。它主要适用于对象和数组类型的数据,对于基础数据类型,reactive 是无效的。如果尝试使用 reactive 来处理基础数据类型,将会得到一个非响应式的对象。
综上所述,Ref 和 Reactive 在 Vue3 中分别用于处理不同类型的数据,以实现响应式编程。
二、Ref 的优势
(一)可处理多种数据类型
Ref 既能声明基本数据类型,也能声明对象和数组,相比之下 Reactive 只能声明引用数据类型(对象)。Vue 提供了 ref() 方法,允许我们创建可以使用任何值类型的响应式 ref。比如可以声明对象类型的响应式数据:const state = ref({});也可以声明数组类型的响应式数据:const state2 = ref([])。使用 ref,开发者可以灵活地处理不同类型的数据,而不受像 reactive 那样只能处理引用数据类型的限制,为开发提供了更大的灵活性。
(二)不易失去响应性
- 重新分配对象时,Ref 不会失去响应性,而 Reactive 可能会在不当操作下失去响应性,如直接整个对象替换或赋值一个 reactive 对象。当给 reactive 的响应式对象直接赋值一个普通对象时,会导致响应性失效。例如:let state = reactive({ count: 0 });state = { count: 1 };在这种情况下,state 将失去响应性。同样,如果在 nextTick 异步方法中给 reactive 的响应式对象赋值一个新的 reactive 对象,DOM 也不会更新,说明失去了响应性。如:
<template>
{{ state }}
</template>
<script setup>
let state = reactive({ count: 0 });
// 在 nextTick 异步方法中修改 state 的值
nextTick(() => {
// 并不会触发修改 DOM ,说明失去响应了
state = reactive({ count: 11 });
});
</script>
而使用 ref 定义对象时,重新分配对象不会失去响应性。例如:let state = ref({ count: 0 });state.value = { count: 1 };
解决 reactive 对象失去响应性的方法有:不要直接整个对象替换,一个个属性赋值,如state.count = 1;使用 Object.assign,如state = Object.assign(state, { count: 1 });。
- 将 Reactive 对象的属性赋值给变量会断开连接,导致失去响应性,而 Ref 不会出现这种情况。当将 reactive 对象的属性赋值给变量时,这个变量和原对象不再共享响应性连接,对该变量的赋值不会影响原来对象的属性值。例如:let state = reactive({ count: 0 });let n = state.count;n++;console.log(state.count) // 0。
解决方案是避免将 reactive 对象的属性赋值给变量。
- 直接解构 Reactive 对象会失去响应性,需使用 toRefs 解构,而 Ref 不存在此问题。直接解构 reactive 对象会导致失去响应性。例如:let state = reactive({ count: 0 });let { count } = state;count++; // state.count 值依旧是 0。
解决方案是使用 toRefs 解构,解构后的属性是 ref 的响应式变量。如:const state = reactive({ count: 0 });let { count } = toRefs(state);count.value++; // state.count 值改变为 1。
三、Reactive 的局限性
(一)值类型有限
Reactive 主要适用于对象,包括数组和一些集合类型,对于基础数据类型无效。相比之下,Ref 既能声明基本数据类型,也能声明对象和数组,为开发提供了更大的灵活性。例如,Vue 提供了 ref() 方法,可以创建不同类型的响应式 ref,如 const state = ref({})(对象类型)、const state2 = ref([])(数组类型)。而尝试使用 reactive 来处理基础数据类型,将会得到一个非响应式的对象。
(二)使用不当易失去响应
- 直接赋值对象可能会导致失去响应性。如果将一个普通对象直接赋值给由 reactive 创建的响应式对象,如 let state = reactive({ count: 0 });state = { count: 1 };,会导致响应性失效。同样,在 nextTick 异步方法中给 reactive 的响应式对象赋值一个新的 reactive 对象,DOM 也不会更新,说明失去了响应性。例如:
<template>
{{ state }}
</template>
<script setup>
const state = reactive({ count: 0 });
// 在 nextTick 异步方法中修改 state 的值
nextTick(() => {
// 并不会触发修改 DOM ,说明失去响应了
state = reactive({ count: 11 });
});
</script>
而使用 ref 定义对象时,重新分配对象不会失去响应性,如 let state = ref({ count: 0 });state.value = { count: 1 };。解决 reactive 对象失去响应性的方法有:不要直接整个对象替换,一个个属性赋值,如state.count = 1;使用 Object.assign,如state = Object.assign(state, { count: 1 });。
- 将 reactive 对象的属性赋值给变量会断开连接,导致失去响应性。当将 reactive 对象的属性赋值给变量时,这个变量和原对象不再共享响应性连接,对该变量的赋值不会影响原来对象的属性值。例如:let state = reactive({ count: 0 });let n = state.count;n++;console.log(state.count) // 0。解决方案是避免将 reactive 对象的属性赋值给变量。
- 直接解构 reactive 对象会失去响应性,需使用 toRefs 解构。直接解构 reactive 对象会导致失去响应性,如 let state = reactive({ count: 0 });let { count } = state;count++; // state.count 值依旧是 0。解决方案是使用 toRefs 解构,解构后的属性是 ref 的响应式变量。如:const state = reactive({ count: 0 });let { count } = toRefs(state);count.value++; // state.count 值改变为 1。
四、结论
总体来说,非必要情况下最好避免使用 Reactive,官方文档也强烈推荐使用 Ref 作为声明响应式状态的主要 API。Ref 更灵活、更不易失去响应性,在 Vue3 中具有明显优势。
Ref 可以处理多种数据类型,包括基本数据类型、对象和数组,而 Reactive 主要适用于对象和数组,对于基础数据类型无效。在使用过程中,Reactive 还存在一些容易失去响应性的情况,如直接赋值对象、将 Reactive 对象的属性赋值给变量、直接解构 Reactive 对象等。相比之下,Ref 在这些方面表现更加稳定,不会轻易失去响应性。
综上所述,在 Vue3 中使用 Ref 可以更好地满足开发需求,提高开发效率和代码的稳定性。
作者:五号厂房 链接:https://juejin.cn/post/7440463179401232411 来源:稀土掘金 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。