🔄 ref 与 reactive 的对比
为了更清晰地理解 ref的定位,可以将其与 reactive进行简单对比:
| 特性 | ref |
reactive |
|---|---|---|
| 目标数据类型 | 基础类型和对象类型 | 仅对象类型(如 Object, Array, Map, Set 等) |
| 返回值 | 一个 RefImpl实例(响应式引用对象) |
一个 Proxy实例(代理对象) |
| 访问值(JS中) | 需要通过 .value属性 |
直接访问属性 |
| 模板中使用 | 自动解包,通常无需 .value |
直接访问属性 |
简单来说,reactive是 Proxy的直接应用,而 ref是为了弥补 Proxy对基础类型无能为力而设计的一个更上层的封装 。当 ref接收一个对象时,其内部实际上会调用 reactive来深度转换这个对象。
🚫 Proxy 的运作对象
Proxy的核心功能是代理一个对象 ,并拦截针对该对象的某些操作(比如属性读取、设置、删除等)。它的目标必须是对象,因为代理操作依赖于对象的属性描述机制。而 JavaScript 中的基础类型(Primitive Values) ,例如 String、Number、Boolean、null、undefined等,本身不是对象 。它们是不可变的,并且不具备可供 Proxy拦截的属性和方法(尽管在运算时,JavaScript 引擎会临时将部分基础类型包装成对象,但这个过程是瞬时的,并非一个可被长期代理的稳定对象)。如果您尝试直接用 Proxy去包装一个基础类型,JavaScript 引擎会抛出错误:
ini
const primitiveValue = 42;
const proxy = new Proxy(primitiveValue, {}); // 抛出 TypeError: Cannot create proxy with a non-object as target or handler
💡 Vue 3 的解决方案:ref
为了解决这个问题,Vue 3 引入了 ref API。它的思路非常巧妙:将基础类型值"包装"在一个对象里面,然后去代理这个包装对象。
-
包装过程 :当您调用
ref(42)时,Vue 3 在内部实际上创建了一个类似这样的结构:iniconst countRef = { value: 42 }; -
响应式实现 :Vue 3 使用
Proxy来代理这个包含value属性的包装对象。这样,所有对countRef.value的读取和设置操作都能被Proxy精确拦截,从而实现响应式。 -
.value的由来 :正因为ref返回的是一个响应式的包装对象,所以在 JavaScript 中访问或修改其值时,需要通过.value属性来操作。iniconst count = ref(0); console.log(count.value); // 读取,触发 Proxy 的 get 拦截 count.value = 1; // 设置,触发 Proxy 的 set 拦截(不过在 Vue 模板中,
ref会被自动解包,通常无需书写.value)
💎 简单来说
Vue 3 的 Proxy无法直接监听基础类型,是因为 Proxy的工作机制要求其目标必须是一个对象。Vue 3 通过 refAPI 巧妙地规避了这一限制,将基础类型值包装成对象后再进行代理,从而实现了对基础类型的响应式跟踪。