1. 适用数据类型不同
ref :主要用于包装 基本数据类型 (String、Number、Boolean、Null、Undefined 等),也可以包装对象 / 数组(内部会自动转换为 reactive代理)。
reactive :仅用于包装 引用数据类型 (Object、Array 等),不能直接用于基本类型(否则会报警告且失去响应性)。
2. 访问 / 修改方式不同
ref :会将数据包装为一个 带有 value属性的响应式对象 ,因此访问 / 修改时需要通过 .value操作(模板中使用时会自动解包,无需 .value)。
import { ref } from 'vue';
const count = ref(0);
// 访问值
console.log(count.value); // 输出: 0
// 修改值
count.value++;
console.log(count.value); // 输出: 1
reactive :返回的是数据的 响应式代理对象 ,可以直接访问 / 修改属性,无需额外操作。
import { reactive } from 'vue';
const user = reactive({
name: '赵六',
age: 22
});
// 访问属性
console.log(user.name); // 输出: 赵六
// 修改属性
user.age = 23;
console.log(user.age); // 输出: 23
3. 解包特性不同
ref的自动解包 :
在模板中使用时,ref会自动解包(无需 .value)。
当 ref作为 reactive对象的属性时,会自动解包(访问时无需 .value)
import { ref, reactive } from 'vue';
<!-- 在模板中,ref 会自动解包,无需 .value -->
<p>ref Count: {{ count }}</p>
const count = ref(0);
const obj = reactive({ count }); // 将 ref 作为 reactive 对象的属性
// 访问时会自动解包,无需 .value
console.log(obj.count); // 输出: 0
// 修改时也会自动解包
obj.count++;
console.log(count.value); // 输出: 1
console.log(obj.count); // 输出: 1
reactive 无自动解包 :
访问属性时必须通过代理对象,直接解构会丢失响应性(需要用 toRefs处理)。
<!-- 在模板中,访问属性时必须通过代理对象-->
<p>reactive Count: {{ user.count }}</p>
const user = reactive({ name: '张三', age: 18 });
const { name } = user; // 解构后name失去响应性
name = '李四'; // 不会触发界面更新
// 正确做法:用toRefs转为ref对象
const { name, age } = toRefs(user);
name.value = '李四'; // 响应性保留
4. 替换数据的差异
ref :可以直接替换整个值(因为本质是对 value的修改),响应性不会丢失。
import { ref } from 'vue';
const user = ref({ name: '吴九', age: 35 });
console.log(user.value.name); // 输出: 吴九
// 直接替换整个对象
user.value = { name: '郑十', age: 40 };
console.log(user.value.name); // 输出: 郑十
// 响应性依然保持,界面会更新显示新的用户信息
reactive :不能直接替换整个对象(会切断与原始代理的联系,导致响应性丢失)。
import { reactive } from 'vue';
const user = reactive({ name: '小明', age: 16 });
// 错误示范:直接替换整个对象,会切断响应性连接
user = { name: '小红', age: 17 }; // 这会报错:Uncaught TypeError: Assignment to constant variable.
// 如果 user 是 let,虽然不报错,但原来的响应式代理丢失,所有依赖它的地方都不会更新
// 正确做法:修改属性而不是替换整个对象
user.name = '小红';
user.age = 17;
// 如果确实需要替换大量数据,可以考虑使用 Object.assign
const newUserData = { name: '小刚', age: 18 };
Object.assign(user, newUserData); // 这样可以保留响应性
5. 类型推断(TypeScript)
ref:类型推断更清晰,直接声明类型即可:
const count = ref<number>(0); // 明确 count 是 number 类型的 ref
count.value = 'hello'; // TypeScript 会报错(类型不匹配)
`
- TypeScript 环境下,需要清晰类型推断的场景。reactive`:类型推断依赖于初始值,若初始值为空对象,需手动声明接口:
interface State {
count: number;
name?: string;
}
const state = reactive<State>({ count: 0 }); // 明确类型
state.count = 'hello'; // TypeScript 报错
6. 使用场景建议
优先用ref :
- 存储基本类型数据(number、string、boolean 等)
- 存储需要独立响应的单一数据(如表单输入值、计数器)
- 数据可能被频繁赋值替换(如
count.value = 0) - TypeScript 环境下,需要清晰类型推断的场景。
优先用 reactive :
- 存储复杂对象 / 数组(如用户信息、列表数据);
- 数据具有多个关联属性(如
user对象包含name、age、address); - 不需要替换整个对象,只需要修改内部属性的场景;
- 希望避免频繁写
.value的场景(在reactive中直接访问属性更简洁)。