1. 定义方式与基础类型支持
-
reactive
:用于创建响应式对象(包括数组、Map、Set 等)。
import { reactive } from 'vue'
const state = reactive({ count: 0 })
- 局限性:无法直接处理基本类型(如number,string,boolean)。
reactive
的本质是通过 ES6 Proxy 拦截对象属性的访问和修改。但基本类型(如数字、字符串)不是对象,没有属性可供拦截,因此无法直接转为响应式。 - 深层响应式:对象的所有嵌套属性都会被转为响应式。当
reactive
处理对象时,不仅对象本身,其所有嵌套层级的属性都会被递归转为响应式。这意味着 Vue 能追踪任何深度的属性变化。
-
ref
:用于创建任意类型的响应式引用,包括基本类型。
import { ref } from 'vue'
const count = ref(0)
- 本质:内部将值包装为一个对象,通过
.value
属性访问。基本类型(如number
、string
)没有属性可供拦截,而ref
通过创建一个包含.value
属性的对象,利用Object.defineProperty
的getter/setter
实现响应式。
import { ref } from 'vue'
const count = ref(0)
// 本质上,count 是这样一个对象:
// {
// value: 0,
// // 内部有 getter/setter 实现响应式
// get value() { /* 追踪依赖 */ },
// set value(newVal) { /* 触发更新 */ }
// }
// 修改值时必须通过 .value
count.value = 1 // 触发响应式更新
- 浅层响应式:仅
.value
是响应式的,若值为对象需使用reactive
嵌套。ref
的响应式特性仅作用于.value
属性,若.value
是对象或数组,需要手动用reactive
嵌套才能实现深层响应式。
const user = ref({ name: 'John' })
// ✅ 修改 .value 触发更新
user.value = { name: 'Jane' } // 整个对象替换
// ❌ 直接修改对象内部属性,非响应式!
user.value.name = 'Jane' // 虽然值变了,但 Vue 无法追踪这个变化
// ✅ 正确做法:用 reactive 嵌套
const user = ref(reactive({ name: 'John' }))
user.value.name = 'Jane' // 现在可以响应式更新
- 灵活性:允许
.value
动态变更类型(如从number
变为object
)。
const value = ref(0) // 初始为数字
// 可以动态变更为对象
value.value = { name: 'Vue' } // 仍然保持响应式
- 性能优化:避免不必要的深层递归,按需使用
reactive
控制响应式深度。
2. 访问方式
reactive
:直接访问对象属性。
console.log(state.count) // 无需.value
ref
:在模板中自动解包,在 JavaScript 中需通过.value
。
// 模板中
<div>{{ count }}</div> // 自动解包,无需.value
// JavaScript中
console.log(count.value) // 需要.value
3. 响应式原理
reactive
:基于 ES6 Proxy 实现,拦截对象的属性访问和修改。
- 深层响应式:自动递归处理所有嵌套对象。
- 更全面的监听:能监听属性的添加、删除、枚举等操作。
- 更好的性能:相比 Vue2 的
Object.defineProperty
,减少了初始化时的递归遍历开销。
ref
:本质是一个包含.value
属性的对象,通过Object.defineProperty
的getter/setter
实现响应式。
4. 使用场景
reactive
:
- 处理复杂对象或嵌套结构。
- 状态管理(如 Vuex/Pinia 的替代方案)。
- 示例:表单数据、组件状态对象。
ref
:
- 基本类型响应式数据。
- 模板中的变量(如计数器、加载状态)。
- 与 DOM 交互(通过
ref
属性)。 - 示例:计数器、表单输入值、API 加载状态。
5. 注意事项
- 解构赋值丢失响应式:
reactive
对象解构后会失去响应性,需使用toRefs
保持。toRefs
的本质是为每个属性创建一个ref
,这个ref
的getter/setter
会代理到原始对象的属性上。
const state = reactive({
count: 0
});
// ❌ 解构赋值导致响应性丢失
const { count } = state;
const increment = () => {
state.count++;
console.log('state.count:', state.count);
console.log('count:', count); // 解构后的 count 不会更新
};
不是同一个对象,但值始终保持同步(下图)
const state = reactive({
count: 0
});
const { count } = toRefs(state);
const checkRelationship = () => {
// 验证 state.count 和 count.value 是否指向同一值
console.log('state.count === count.value:', state.count === count.value); // true
// 验证 count 是否是 ref 对象
console.log('count 是否是 ref 对象:', Vue.isRef(count)); // true
// 验证 state.count 是否是 ref 对象
console.log('state.count 是否是 ref 对象:', Vue.isRef(state.count)); // false
// 修改 state.count,观察 count.value
state.count++;
console.log('修改后: state.count =', state.count, 'count.value =', count.value);
// 修改 count.value,观察 state.count
count.value++;
console.log('再修改后: state.count =', state.count, 'count.value =', count.value);
};
- 数组和对象优先用
reactive
:ref
适合简单值,复杂结构用reactive
更自然。
6. 选择建议
- 使用
ref
:
- 数据类型可能变化(如从
number
变为object
)。 - 处理基本类型。
- 需要与 Vue 的模板系统直接交互。
- 使用
reactive
:
- 处理对象、数组等复杂结构。
- 需要深层响应式。
- 避免频繁使用
.value
。
总结:
ref
是通用解决方案,适合所有类型;reactive
更适合复杂对象,避免.value
的繁琐。根据场景灵活选择,可以混合使用以达到最佳效果。