vue 3 ref原理
Vue 3 的 ref 主要用于包装基础类型(如 number/string)使其成为响应式对象,同时也能处理对象/数组(此时内部转为 reactive)。以下是其核心实现思路及关键代码逻辑:
核心设计目标
- 基础类型支持:通过对象包装让原始值具备响应式能力。
- 统一访问接口:用
.value属性统一操作值,简化心智模型。 - 自动解包优化:在模板中自动解包
.value,提升开发体验。
核心实现结构
构造函数 RefImpl
class RefImpl<T> {
private _value: T
private _rawValue: T // 存储原始值,用于比对变化
public dep: Dep = new Set() // 依赖集合
public __v_isRef = true // 标识是 ref 对象
constructor(value: T) {
this._rawValue = value
// 如果值是对象,用 reactive 包裹
this._value = isObject(value) ? reactive(value) : value
}
get value() {
trackRefValue(this) // 收集依赖(与 reactive 共享 targetMap)
return this._value
}
set value(newValue) {
// 新旧值对比(需处理对象/NaN等特殊情况)
if (hasChanged(newValue, this._rawValue)) {
this._rawValue = newValue
this._value = isObject(newValue) ? reactive(newValue) : newValue
triggerRefValue(this) // 触发依赖更新
}
}
}
关键函数实现
创建 ref (ref())
function ref(value) {
// 已经是 ref 则直接返回
if (isRef(value)) return value;
// 包裹基本类型/对象
return new RefImpl(value);
}
依赖收集 (trackRefValue)
function trackRefValue(ref) {
if (activeEffect) {
trackEffects(ref.dep || (ref.dep = new Set()));
}
}
触发更新 (triggerRefValue)
function triggerRefValue(ref) {
if (ref.dep) {
triggerEffects(ref.dep);
}
}
判断 Ref (isRef)
function isRef(r) {
return !!(r && r.__v_isRef === true);
}
技术要点解析
-
自动对象转换
当ref接受对象时,内部自动调用reactive:const objRef = ref({ count: 0 }) objRef.value.count++ // 触发更新(因内部是 reactive) -
值变更比对优化
使用Object.is()处理NaN与+0/-0的特殊情况:function hasChanged(newVal, oldVal) { return !Object.is(newVal, oldVal); } -
模板自动解包
在模板中直接使用ref会省略.value:<template> <!-- 直接写 count 而非 count.value --> <div>{{ count }}</div> </template> <script setup> const count = ref(0) // 编译时自动解包 </script> -
响应式解包机制
当ref嵌套在reactive对象中时,自动解包.value:const r = ref(0); const obj = reactive({ r }); console.log(obj.r); // 输出 0,而非 ref 对象
边界处理与工具函数
-
unref工具函数:解包ref或返回原值function unref(ref) { return isRef(ref) ? ref.value : ref; } -
处理只读
ref(readonly(ref(0))):
通过shallowReadonly阻断.value修改:function readonlyRef(ref) { return Object.freeze({ get value() { return ref.value } }); } -
自定义 Ref (
customRef):
手动控制依赖跟踪与触发逻辑:function customRef(factory) { class CustomRefImpl extends RefImpl { constructor(getter, setter) { super(undefined); // 用户自定义 track/trigger 逻辑 } } return new CustomRefImpl(factory()); }
性能优化
-
惰性依赖收集
仅当在effect中访问.value时才收集依赖,避免不必要的内存开销。 -
轻量级对象包装
RefImpl类比reactive的Proxy更轻量,尤其适合基础类型。 -
复用依赖存储结构
共用全局的targetMap(WeakMap) 来管理所有响应式对象的依赖关系。
与 Reactive 的关系对比
| 特性 | ref |
reactive |
|---|---|---|
| 主要用途 | 包装基础类型或任意对象 | 包装对象/数组 |
| 访问方式 | .value |
直接访问属性 |
| 模板解包 | 自动解包(除嵌套在对象内部) | 无特殊处理 |
| 内部实现 | getter/setter + 依赖集合 |
Proxy + 递归代理 |
| 性能对比 | 更高频次基础类型操作的优化 | 适合复杂对象结构 |
使用示例
// 基础类型
const count = ref(0)
effect(() => console.log(count.value)) // -> 0
count.value++ // 触发 effect -> 1
// 对象类型
const user = ref({ name: 'Alice' })
user.value.name = 'Bob' // 触发更新(内部是 reactive)
// DOM 元素引用
const inputRef = ref(null)
onMounted(() => inputRef.value.focus())
总结
ref 通过包装对象 + 访问器(getter/setter)实现了对各类值的响应式支持,其核心思想是:将基础类型转化为引用类型以利用响应式系统。设计 .value 的访问方式虽然增加了心智负担,但换来更灵活的类型支持与模板优化,成为组合式 API 的基础工具之一。

浙公网安备 33010602011771号