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. 终极理解:refreactive({ 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'

    • 这会触发 reactive Proxy 的 set 拦截,保持响应式!

  • nameRef_B (来自 ref)

    • 读取:nameRef_B.value

    • 修改:nameRef_B.value = 'Bob'

    • 这会触发 ref 内部的 set 机制,保持响应式!

结论: ref 的设计,本质上就是为了解决“变量重新赋值”会导致响应式丢失的问题。

ref() 通过在内部创建一个包装对象(这个对象就像 { value: 'xxx' }),并提供一个 .value 属性,巧妙地将你对一个值(尤其是原始值)的“赋值操作”,转换成了对一个响应式对象(reactive 对象)的“属性修改操作”

这就是为什么 ref 必须有 .value,而 reactive 不需要——因为 reactive 本身就是那个“箱子”,而 ref 是在帮你制造那个“箱子”。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐