export function ref(value?: unknown) {
return createRef(value, false);
}
function createRef(rawValue: unknown, shallow: boolean) {
if (isRef(rawValue)) {
return rawValue;
}
return new RefImpl(rawValue, shallow);
}
class RefImpl<T> {
private _value: T;
private _rawValue: T;
public readonly __v_isRef = true;
constructor(value: T, public readonly __v_isShallow: boolean) {
this._rawValue = value;
this._value = __v_isShallow ? value : toReactive(value);
}
get value() {
trackRefValue(this); // 依赖收集
return this._value;
}
set value(newVal) {
if (hasChanged(newVal, this._rawValue)) {
this._rawValue = newVal;
this._value = this.__v_isShallow ? newVal : toReactive(newVal);
triggerRefValue(this); // 触发更新
}
}
}
3. value
是响应式系统中的依赖收集和触发更新的关键
- 在
RefImpl
类中,get value()
方法是一个 getter,它在访问ref.value
时会调用trackRefValue(this)
,这个函数负责在依赖中注册当前的ref
,从而实现 依赖收集。 - 当
set value(newVal)
被调用时,triggerRefValue(this)
会被触发,用来通知依赖这个ref
的组件或函数进行更新。
这种基于 getter 和 setter 的机制是 Vue 3 响应式系统的核心,通过 ref
的 .value
属性,Vue 能够精确地追踪数据的变化,并自动更新视图。
4. 原始类型与对象类型的区别
- Vue 3 对象类型的响应式使用
reactive
,它内部通过 Proxy 直接对对象的属性进行代理。 - 但是对于 原始类型(如
number
、string
),它们并不是对象,无法通过 Proxy 实现响应式,所以需要通过ref
来封装。 ref
的.value
设计是为了统一对各种类型(包括原始类型和对象)的操作方式,确保响应式系统对不同类型数据的追踪和更新。
5. 响应式追踪机制(依赖收集)
trackRefValue(this)
是依赖收集的关键,它在get value()
时被调用,目的是让依赖该ref
值的副作用(例如视图更新函数)记录当前依赖的ref
。- 当
.value
被修改时,triggerRefValue(this)
会触发依赖该值的所有副作用重新运行,以此实现响应式更新。
6. 为什么不直接用 ref
本身来存取值?
- 使用
.value
而不直接使用ref
本身访问值,主要是为了区分响应式的引用和非响应式的普通变量。 - 如果直接使用
ref
本身访问值,而不通过.value
,Vue 无法清楚地区分什么时候应该进行响应式追踪和触发更新。这种分离设计能确保 Vue 内部可以对所有类型的数据进行统一处理。
7. 防止与其他响应式对象混淆
.value
的设计可以避免ref
和reactive
之间的混淆。reactive
直接作用于对象,而ref
是对基础类型和对象的封装。通过这种明确的访问模式,Vue 在内部可以轻松区分ref
和普通对象。
结论
从源码角度来看,Vue 3 中 ref
的 .value
设计是为了:
- 实现原始类型的响应式封装,因为 Proxy 无法直接代理原始类型。
- 通过
.value
来实现对值的 依赖收集 和 触发更新,确保响应式系统能够追踪数据变化并更新视图。 - 明确区分响应式的
ref
和普通数据,保证 Vue 3 响应式系统的一致性与正确性。
这就是为什么在 Vue 3 中,ref
需要通过 .value
进行值的存取。