目录
- 一、核心区别速览
- 二、代码演示
- [1. 数据类型不同](#1. 数据类型不同)
- [2. 访问方式不同](#2. 访问方式不同)
- [3. 重新赋值的区别(重要!)](#3. 重新赋值的区别(重要!))
- [4. 解构的区别](#4. 解构的区别)
- 三、底层实现原理
- [reactive 的实现](#reactive 的实现)
- [ref 的实现(简化版)](#ref 的实现(简化版))
- 四、使用场景建议
- [什么时候用 reactive?](#什么时候用 reactive?)
- [什么时候用 ref?](#什么时候用 ref?)
- 五、常见误区与注意点
- [误区1:reactive 的对象可以直接赋值](#误区1:reactive 的对象可以直接赋值)
- [误区2:ref 在模板中需要 .value](#误区2:ref 在模板中需要 .value)
- [误区3:ref 传入对象和 reactive 的关系](#误区3:ref 传入对象和 reactive 的关系)
- 六、面试回答模板(满分答案)
一、核心区别速览
| 对比维度 | reactive |
ref |
|---|---|---|
| 数据类型 | 只能代理对象/数组 | 可以代理任意类型(基本类型 + 对象) |
| 底层实现 | 直接使用 Proxy |
对象用 reactive,基本类型用 getter/setter |
| 访问方式 | 直接访问 state.count |
需要通过 .value count.value |
| 模板中使用 | 直接使用 | 自动解包,不需要 .value |
| 重新赋值 | ❌ 会丢失响应式 | ✅ 保持响应式 |
| 解构 | ❌ 会丢失响应式 | ✅ 用 toRefs 解构 |
二、代码演示
1. 数据类型不同
javascript
import { reactive, ref } from 'vue';
// reactive:只能用于对象/数组
const state = reactive({ count: 0 }); // ✅
const arr = reactive([1, 2, 3]); // ✅
const num = reactive(0); // ❌ 警告:reactive 不能用于基本类型
// ref:可以用于任意类型
const count = ref(0); // ✅ 基本类型
const user = ref({ name: '张三' }); // ✅ 对象
const list = ref([1, 2, 3]); // ✅ 数组
2. 访问方式不同
javascript
const state = reactive({ count: 0 });
state.count++; // 直接访问
const count = ref(0);
count.value++; // 需要通过 .value
// 但在模板中,ref 会自动解包
// <template>
// {{ count }} ← 不需要 .value
// </template>
3. 重新赋值的区别(重要!)
javascript
// ❌ reactive:重新赋值会丢失响应式
let state = reactive({ count: 0 });
state = { count: 1 }; // 丢失响应式!state 不再是代理对象
// ✅ ref:重新赋值保持响应式
let count = ref(0);
count.value = 1; // 响应式还在
count = ref(10); // 重新赋值整个 ref,仍然响应式
4. 解构的区别
javascript
// ❌ reactive:解构会丢失响应式
const state = reactive({ count: 0, name: '张三' });
const { count, name } = state;
count++; // 不会触发视图更新,只是普通变量
// ✅ 解决方案:使用 toRefs
const { count, name } = toRefs(state);
count.value++; // 保持响应式
// ✅ ref:单独使用没问题
const count = ref(0);
let myCount = count; // 直接赋值引用,保持响应式
myCount.value++; // 会触发更新
三、底层实现原理
reactive 的实现
javascript
function reactive(target) {
// 只处理对象类型
if (!isObject(target)) {
console.warn('reactive 只能用于对象');
return target;
}
return new Proxy(target, {
get(target, key, receiver) {
const res = Reflect.get(target, key, receiver);
// 依赖收集
track(target, key);
// 懒代理:如果返回值是对象,递归代理
if (isObject(res)) {
return reactive(res);
}
return res;
},
set(target, key, value, receiver) {
const result = Reflect.set(target, key, value, receiver);
// 触发更新
trigger(target, key);
return result;
}
});
}
ref 的实现(简化版)
javascript
function ref(value) {
// 创建一个对象,带上 .value 属性
const refObject = {
_value: value,
get value() {
// 收集依赖
track(refObject, 'value');
return this._value;
},
set value(newVal) {
this._value = newVal;
// 触发更新
trigger(refObject, 'value');
}
};
// 如果传入的是对象,用 reactive 包装
if (isObject(value)) {
refObject._value = reactive(value);
}
return refObject;
}
关键点:
-
ref本质是创建了一个有.value属性的对象 -
基本类型:通过 getter/setter 实现响应式
-
对象类型:内部调用
reactive处理
四、使用场景建议
什么时候用 reactive?
javascript
// ✅ 适合:数据结构明确、属性会变化的对象
const state = reactive({
user: { name: '张三', age: 18 },
list: [1, 2, 3]
});
// ✅ 适合:表单数据
const form = reactive({
username: '',
password: '',
email: ''
});
什么时候用 ref?
javascript
// ✅ 适合:基本类型数据
const count = ref(0);
const message = ref('');
const isLoading = ref(false);
// ✅ 适合:需要整体替换的数据
const user = ref(null);
// 后来获取数据后整体赋值
user.value = { name: '张三', age: 18 };
// ✅ 适合:不确定类型的数据
const data = ref(null); // 可能是对象、数组、基本类型
五、常见误区与注意点
误区1:reactive 的对象可以直接赋值
javascript
// ❌ 错误
let state = reactive({ count: 0 });
state = { count: 1 }; // 丢失响应式
// ✅ 正确:修改属性,不要重新赋值
state.count = 1;
// ✅ 或者用 Object.assign
Object.assign(state, { count: 1 });
误区2:ref 在模板中需要 .value
javascript
// ❌ 错误理解
// <template>{{ count.value }}</template> ← 不需要!
// ✅ 正确
// <template>{{ count }}</template> ← 自动解包
误区3:ref 传入对象和 reactive 的关系
javascript
const objRef = ref({ name: '张三' });
// objRef.value 实际上是一个 reactive 代理对象
console.log(objRef.value === reactive({ name: '张三' })); // false
// 但效果相同,都是响应式的
六、面试回答模板(满分答案)
问:reactive 和 ref 的区别是什么?
回答:
主要有以下几点区别:
数据类型 :
reactive只能代理对象和数组,ref可以代理任意类型(包括基本类型)。底层实现 :
reactive直接使用Proxy实现响应式;ref内部创建一个带有.value属性的对象,基本类型通过 getter/setter 实现,对象类型则内部调用reactive。访问方式 :
reactive的数据可以直接访问,ref需要通过.value访问(模板中自动解包,不需要.value)。重新赋值 :
reactive重新赋值整个对象会丢失响应式,ref重新赋值.value或整个ref都能保持响应式。解构 :
reactive直接解构会丢失响应式,需要配合toRefs使用;ref解构后仍保持响应式。使用建议 :对象/表单数据用
reactive,基本类型和需要整体替换的数据用ref。