文章目录
- 一、 Vue 3 响应式原理剖析:ref 与 reactive 的深度区别
-
- 1. 核心特性对比
- 2. 底层原理剖析
-
- reactive:基于 Proxy 的代理
- ref:对象的“包装盒”
- 3. 为什么 reactive 不能直接赋值?
- 二、Vue 3 响应式侦听器:watch vs watchEffect
-
- 1. 核心区别对照表
- 2. watch:精细化的指挥官
- 3. watchEffect:高效的打工人
一、 Vue 3 响应式原理剖析:ref 与 reactive 的深度区别
在 Vue 3 的 Composition API 中,ref 和 reactive 是构建响应式数据的基石。理解它们的底层差异,能帮助你避免许多莫名其妙的 Bug。
1. 核心特性对比
| 接受类型 | 任意类型 (基本类型 + 引用类型) | 仅限引用类型 (Object/Array/Map/Set) |
| 访问方式 | 脚本中需 .value,模板中自动解包 | 直接访问属性 |
| 重新赋值 | 支持修改 .value(响应式不丢失) | 不支持直接替换对象(会丢失响应式) |
| 解构 | 直接解构会丢失响应式 | 直接解构会丢失响应式(需配合 toRefs) |
| 底层实现 | RefImpl 类 + 访问器属性 (getter/setter) | Proxy 代理对象 |
2. 底层原理剖析
reactive:基于 Proxy 的代理
reactive 的核心是 ES6 的 Proxy。它直接拦截整个对象的操作。
- 工作机制:当你访问或修改属性时,Vue 会通过 Proxy 的 get 和 set 拦截器进行依赖收集(Track)和派发更新(Trigger)。
- 局限:Proxy 只能代理对象。如果你传入一个字符串或数字,它无法拦截这些值的变化。
ref:对象的“包装盒”
ref 是为了解决基本类型(String, Number 等)无法响应式而设计的。
- 核心矛盾:ES6 的 Proxy 只能代理 对象,无法代理 基本类型(如数字 0)。
为了统一操作接口,让基本类型也能拥有响应式能力,Vue 必须人为地制造一个对象(即 RefImpl 的实例),并将值挂载到 .value 属性上。
- 通过 get value() 收集依赖。
- 通过 set value() 触发更新。
- 即:ref(obj)
≈
\\approx
≈ new RefImpl(reactive(obj))。
// 简化版的 RefImpl 伪代码
class RefImpl {
constructor(value) {
this._value = value;
}
get value() {
track(this); // 收集依赖
return this._value;
}
set value(newValue) {
if (hasChanged(newValue, this._value)) {
this._value = newValue;
trigger(this); // 触发更新
}
}
}
3. 为什么 reactive 不能直接赋值?
这是 reactive 最典型的“坑”。
let state = reactive({ count: 0 });
// ❌ 错误操作:这会导致响应式丢失
state = { count: 1 };
reactive 返回的是原始对象的 Proxy 代理引用。当你执行 state = { … } 时,变量 state 指向了内存中一个全新的普通对象,而 Vue 追踪的是原来的那个代理地址。 相比之下,ref 允许 count.value = {},因为它修改的是 RefImpl 实例内部的属性,实例本身的引用没变。
二、Vue 3 响应式侦听器:watch vs watchEffect
在 Vue 3 中,watch 和 watchEffect 都是核心的响应式侦听工具,但它们的设计哲学完全不同。简单来说:watch 是“精确制导”,watchEffect 是“自动追踪”。
1. 核心区别对照表
| 副作用追踪 | 手动指定(显式声明侦听源) | 自动追踪(回调里用了谁就侦听谁) |
| 触发时机 | 默认懒执行(数据变了才跑) | 立即执行(初始化时先跑一次) |
| 访问旧值 | 支持(提供 oldValue) | 不支持(只能拿到当前值) |
| 精确度 | 高(可精确控制触发源) | 低(内部任何变量变化都会触发) |
2. watch:精细化的指挥官
watch 需要你明确告诉它:“盯着这个变量,变了就告诉我。”
- 惰性:除非设置 { immediate: true },否则在组件初始化时不会执行。
- 对比性:它是唯一能让你对比“之前是多少,现在是多少”的手段。
- 支持多种源:可以侦听 ref、reactive 对象的属性、甚至是一个 getter 函数。
const count = ref(0);
// 只有当 count 变化时才会触发
watch(count, (newValue, oldValue) => {
console.log(`从 ${oldValue} 变成了 ${newValue}`);
});
3. watchEffect:高效的打工人
watchEffect 不需要你指定谁,它会像一个“扫描仪”,自动识别回调函数中用到的响应式数据。
-
自动依赖收集:不需要显式传递依赖源。
-
立即执行:代码运行到这一行,立刻就会跑一遍(为了收集依赖)。
-
代码更简洁:适合逻辑中涉及多个变量,且只要其中一个变了就重新运行的情况。



![[项目] --- 基于 Spring Boot + Redis的多设计模式抽奖系统-171主机测评](https://www.171host.com/wp-content/uploads/2026/11/20261031220757-6ae666bdaa3f5-220x150.png)