Vue3:ref 与 reactive 的深度区别、watch vs watchEffect
·
文章目录
一、 Vue 3 响应式原理剖析:ref 与 reactive 的深度区别
在 Vue 3 的 Composition API 中,ref 和 reactive 是构建响应式数据的基石。理解它们的底层差异,能帮助你避免许多莫名其妙的 Bug。
1. 核心特性对比
| 特性 | ref |
reactive |
|---|---|---|
| 接受类型 | 任意类型 (基本类型 + 引用类型) | 仅限引用类型 (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 属性上。
- 基本类型:Vue 将其封装成一个名为
RefImpl的类实例。- 通过
get value()收集依赖。 - 通过
set value()触发更新。
- 通过
- 对象类型:如果你给
ref传了一个对象,它内部会自动调用reactive。- 即:
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. 核心区别对照表
| 特性 | watch | watchEffect |
|---|---|---|
| 副作用追踪 | 手动指定(显式声明侦听源) | 自动追踪(回调里用了谁就侦听谁) |
| 触发时机 | 默认懒执行(数据变了才跑) | 立即执行(初始化时先跑一次) |
| 访问旧值 | 支持(提供 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 不需要你指定谁,它会像一个“扫描仪”,自动识别回调函数中用到的响应式数据。
-
自动依赖收集:不需要显式传递依赖源。
-
立即执行:代码运行到这一行,立刻就会跑一遍(为了收集依赖)。
-
代码更简洁:适合逻辑中涉及多个变量,且只要其中一个变了就重新运行的情况。
更多推荐


所有评论(0)