ref的原理
Vue 3 的 ref 是响应式系统的核心 API 之一,其原理基于对象包装和访问器拦截,实现对基本类型和对象类型的响应式支持。以下是其核心实现机制的详细解析:
⚙️ 一、设计目标与核心思想
- 支持基本类型
JavaScript 的Proxy无法直接代理基本类型(如number/string),因此ref通过对象包装({ value: 原始值 })将基本类型转化为引用类型,从而利用响应式系统。 - 统一访问接口
所有类型均通过.value属性访问值,简化心智模型:
const count = ref(0); // 创建
count.value++; // 修改
- 模板自动解包
在模板中无需.value,Vue 编译时自动解包:
<!-- 自动解包:直接写 count 而非 count.value -->
<div>{{ count }}</div>
🧠 二、核心实现结构
ref 的本质是包含 value 访问器的对象,其简化代码如下:
class RefImpl {
private _value: T; // 存储当前值
private _rawValue: T; // 存储原始值(用于比对变更)
public dep: Dep = new Set(); // 依赖集合
constructor(value: T) {
this._rawValue = value;
// 若为对象,递归调用 reactive 转为响应式
this._value = isObject(value) ? reactive(value) : value;
}
get value() {
trackRefValue(this); // 收集依赖(如组件渲染函数)
return this._value;
}
set value(newValue) {
// 值变更比对(处理 NaN/±0 等边界)
if (hasChanged(newValue, this._rawValue)) {
this._rawValue = newValue;
this._value = isObject(newValue) ? reactive(newValue) : newValue;
triggerRefValue(this); // 触发依赖更新
}
}
}
- 关键函数:
trackRefValue():依赖收集,关联当前ref与副作用(如视图更新)。triggerRefValue():值变更时触发所有关联副作用。
🔄 三、响应式原理详解
- 依赖收集(Track)
当在副作用(如组件的render函数)中读取ref.value时,触发track将当前副作用存入ref.dep。 - 触发更新(Trigger)
修改ref.value时,调用trigger遍历ref.dep中所有副作用并执行。 - 值变更比对优化
使用Object.is()比对值是否变化,避免无效更新:
function hasChanged(newVal, oldVal) {
return !Object.is(newVal, oldVal); // 处理 NaN 和 ±0
}
🧩 四、特殊场景处理机制
- 对象类型自动转换
若ref的值为对象,内部自动调用reactive递归代理其属性:
const objRef = ref({ count: 0 });
objRef.value.count++; // 触发更新(因内部是 reactive 代理)
- 嵌套在 reactive 中自动解包
当ref作为reactive的属性时,自动解包.value:
const count = ref(0);
const state = reactive({ count });
console.log(state.count); // 0(自动解包,无需 .value)
- 模板编译优化
Vue 编译器将模板中的ref直接替换为value属性访问:
<!-- 编译前 -->
<div>{{ count }}</div>
<!-- 编译后 -->
h('div', count.value);
⚖️ 五、Ref 与 Reactive 对比
| 特性 | ref | reactive |
|---|---|---|
| 适用类型 | 基本类型 + 对象类型 | 仅对象/数组/集合 |
| 实现方式 | getter/setter + 依赖集合 | Proxy 递归代理 |
| 访问方式 | 需 .value(模板自动解包) | 直接访问属性 |
| 解构响应性 | 解构后仍为响应式引用 | 解构需 toRefs 保持响应性 |
| 性能 | 轻量,适合高频修改的基本类型 | 适合深嵌套对象 |
💡 六、最佳实践与注意事项
- 优先场景
ref:基本类型、DOM 引用、需整体替换的对象。reactive:复杂嵌套对象(如表单状态)。
- 避免响应式丢失
- 解构
ref对象时无需特殊处理,但reactive需配合toRefs。 - 禁止直接替换
reactive对象(用Object.assign合并更新)。
- 解构
- 性能优化
- 减少不必要的
.value访问(合并计算)。 - 复杂计算逻辑用
computed缓存结果。
- 减少不必要的
💎 总结
ref 的核心原理是通过对象包装 + 访问器拦截实现响应式,解决了基本类型无法被 Proxy 代理的问题。其设计巧妙结合了 .value 的统一接口、模板自动解包、对象类型递归转换等机制,成为 Vue 3 响应式系统的基石。理解 track/trigger 的依赖管理机制和值变更比对优化,有助于在开发中高效使用 ref 并规避响应式丢失的常见问题。
评论