Vue3:深入浅出为什么ref(‘xxx‘) ≈ reactive({ value: ‘xxx‘ })
1. 回顾:reactive 为什么会“失联”?
Vue3:从浅/深拷贝深入浅出reactive响应式缺失的原因
这是我们讨论的基础。
import { reactive } from 'vue';
let state = reactive({ count: 1 });
// 1. ✅ 这样操作:保持响应式
state.count = 2;
// 为什么?
// 我们在操作 state 这个 Proxy 对象。
// Proxy 拦截了 .count 的 'set' 操作,并通知 Vue 更新。
// 2. ❌ 这样操作:丢失响应式
state = { count: 2 };
// 为什么?
// 这是“变量重新赋值” (Variable Reassignment)。
// 变量 state 之前指向【Proxy A】。
// 现在你让 state 变量指向了一个全新的、普通的 JavaScript 对象【Object B】。
// 【Proxy A】还在,但你的 state 变量不再引用它了。
// 当你修改新对象 { count: 2 } 时,它不是 Proxy,Vue 根本不知道。
核心结论: 响应式是绑定在原始的 Proxy 对象上的,而不是绑定在 state 这个变量上的。一旦变量指向了新对象,链接就断了。
2. 困境:原始值 (Primitives) 怎么办?
现在,我们想让一个原始值(如字符串、数字)也具有响应式:
let name = 'Alice';
如果我们想把它改成 'Bob':
name = 'Bob';
这 100% 是变量重新赋值。根据我们上面的结论,这种操作永远无法被 Vue 拦截。reactive 只能作用于对象({})和数组([]),它无法拦截这种变量赋值。
3. 解决方案:“装箱” (Boxing)
既然我们不能拦截“变量赋值”,但可以拦截“对象属性修改” (如 state.count = 2),那我们该怎么办?
答案是:把原始值“装”到一个对象里。
我们不直接操作 name,而是创建一个“箱子”(一个包装对象),把 name 放进去:
// 原始思路
let name = 'Alice';
// "装箱"思路
const nameWrapper = {
value: 'Alice' // 我们把值存在一个叫 'value' 的属性里
};
现在,我们不碰 nameWrapper 这个变量,只操作它的属性:
-
读取:
nameWrapper.value -
修改:
nameWrapper.value = 'Bob'
这一下就变成了我们熟悉的“对象属性修改”操作了!
4. 终极理解:ref ≈ reactive({ value: 'xxx' })
现在,把“装箱”和 reactive 结合起来:
import { reactive, ref } from 'vue';
// 我们想让 'Alice' 变成响应式
// 1. 手动“装箱”
const wrapper = { value: 'Alice' };
// 2. 把“箱子”变成响应式
const nameRef_A = reactive(wrapper);
// --- 这就是 ref('Alice') 在做的事情 ---
// Vue 帮你自动“装箱”并使其响应式
const nameRef_B = ref('Alice');
现在我们来对比一下:
-
nameRef_A(来自 reactive)-
读取:
nameRef_A.value -
修改:
nameRef_A.value = 'Bob' -
这会触发
reactiveProxy 的set拦截,保持响应式!
-
-
nameRef_B(来自 ref)-
读取:
nameRef_B.value -
修改:
nameRef_B.value = 'Bob' -
这会触发
ref内部的set机制,保持响应式!
-
结论: ref 的设计,本质上就是为了解决“变量重新赋值”会导致响应式丢失的问题。
ref() 通过在内部创建一个包装对象(这个对象就像 { value: 'xxx' }),并提供一个 .value 属性,巧妙地将你对一个值(尤其是原始值)的“赋值操作”,转换成了对一个响应式对象(reactive 对象)的“属性修改操作”。
这就是为什么 ref 必须有 .value,而 reactive 不需要——因为 reactive 本身就是那个“箱子”,而 ref 是在帮你制造那个“箱子”。
更多推荐


所有评论(0)