Vue3 响应式数据:ref()
响应式数据
Owner: fredbrock
目的
了解 ref() 函数的创建过程,以及一系列工具函数的作用。
ref()
接收一个参数,将其包装在一个 .value
属性的 ref对象
。
createRef()
用于创建 Ref 实例的工厂函数。
function createRef(rawValue: unknown, shallow: boolean) {
if (isRef(rawValue)) {
return rawValue
}
return new RefImpl(rawValue, shallow)
}
RefImpl
Ref 实例的构造函数。
class RefImpl<T> {
private _value: T
private _rawValue: T
public dep?: Dep = undefined
public readonly __v_isRef = true
constructor(
value: T,
public readonly __v_isShallow: boolean,
) {
this._rawValue = __v_isShallow ? value : toRaw(value)
this._value = __v_isShallow ? value : toReactive(value)
}
get value() {
trackRefValue(this)
return this._value
}
set value(newVal) {
const useDirectValue =
this.__v_isShallow || isShallow(newVal) || isReadonly(newVal)
newVal = useDirectValue ? newVal : toRaw(newVal)
if (hasChanged(newVal, this._rawValue)) {
const oldVal = this._rawValue
this._rawValue = newVal
this._value = useDirectValue ? newVal : toReactive(newVal)
triggerRefValue(this, DirtyLevels.Dirty, newVal, oldVal)
}
}
}
转换成 JS
class RefImpl {
constructor(value, __v_isShallow) {
this.__v_isShallow = __v_isShallow;
this.dep = undefined;
this.__v_isRef = true;
this._rawValue = __v_isShallow ? value : toRaw(value);
this._value = __v_isShallow ? value : toReactive(value);
}
get value() {
trackRefValue(this);
return this._value;
}
set value(newVal) {
const useDirectValue = this.__v_isShallow || isShallow(newVal) || isReadonly(newVal);
newVal = useDirectValue ? newVal : toRaw(newVal);
if (hasChanged(newVal, this._rawValue)) {
const oldVal = this._rawValue;
this._rawValue = newVal;
this._value = useDirectValue ? newVal : toReactive(newVal);
triggerRefValue(this, DirtyLevels.Dirty, newVal, oldVal);
}
}
}
__v_isShallow
: 用于判断创建 ref
还是 shallowRef
。
dep
:一个依赖订阅器,用于收集依赖,是一个 Map 构造函数的实例。
__v_isRef
:用于标识 ref
实例,方便后续判断值是否为 ref。
_rawValue
:保存传入ref(val)的原始值。
_value
:将传入的值转换为响应式的值。
get value():
访问实例 .value
属性时,触发 trackRefValue()
, 并且返回 _value
。
set value(newVal):
设置实例.value
属性时,声明一个常量 oldVal
触发变化前的值,并且设置私有属性_rawValue
和 _value
为新的值,触发 triggerRefValue()
。
trackRefValue()
trackRefValue 主要用于创建 dep 依赖。dep是一个Map的实例。
export function trackRefValue(ref: RefBase<any>) {
if (shouldTrack && activeEffect) {
ref = toRaw(ref)
trackEffect(
activeEffect,
(ref.dep ??= createDep(
() => (ref.dep = undefined),
ref instanceof ComputedRefImpl ? ref : undefined,
)),
__DEV__
? {
target: ref,
type: TrackOpTypes.GET,
key: 'value',
}
: void 0,
)
}
}
triggerRefValue()
export function triggerRefValue(
ref: RefBase<any>,
dirtyLevel: DirtyLevels = DirtyLevels.Dirty,
newVal?: any,
oldVal?: any,
) {
ref = toRaw(ref)
const dep = ref.dep
if (dep) {
triggerEffects(
dep,
dirtyLevel,
__DEV__
? {
target: ref,
type: TriggerOpTypes.SET,
key: 'value',
newValue: newVal,
oldValue: oldVal,
}
: void 0,
)
}
}
跟多 Effect 细节在 effect章节在详细解读。
其他工具函数
shallowRef()
浅层响应式Ref()。
triggerRef()
强制触发 shallowRef()
const state = shallowRef({ count: 1 })
// 不会触发更改
state.value.count = 2
triggerRef(shallow)
customRef()
创建一个自定义的 Ref,显式声明对其依赖追踪和更新触发的控制方式。
isRef(val)
用于判断是否是一个Ref值。
内部再创建Ref值的时候会创建一个私有只读属性,__v_isRef
并且值设置为 true
,那么判断的时候就可以根据该属性判断是否是Ref
值。
unref()
如果参数是 ref,则返回内部值,否则返回参数本身。
实现:return isRef(ref) ? ref.value : ref
toValue()
接收一个值、ref、 getter函数,返回值。
实现:return isFunction(source) ? source() : unref(source)
const a = ref(0);
const b = ref(true);
const c = ref([1, 2, 3]);
const d = ref({ name: "张三" });
console.log(toValue(a)); // 返回数字 0
console.log(toValue(b)); // 返回数字 0
console.log(toValue(c)); // 返回 Proxy(Array) {0: 1, 1: 2, 2: 3}
console.log(toValue(d)); // 返回 Proxy(Object) {name: '张三'}
toRefs()
作用:解构/扩展返回的对象而不会丢失响应性。
返回值: 返回一个普通对象,将每个属性值转换成 Ref。
const a = reactive({
name: "张三",
age: 18,
});
const b = toRefs(a);
b.name.value = "李四";
console.log(a.name); // 返回 李四
console.log(b.name.value); // 返回数字 李四
总结
- 在setup钩子函数中调用 ref() 创建响应式变量。
- ref() 内部通过 createRef() 工厂函数执行返回一个值,如果值是 Ref 实例,直接返回该值,否则通过 RefImpl 构造函数创建一个实例,并返回该实例。
- RefImpl 是一个类构造函数,该构造函数主要通过 ES6 的 get/set 函数用来拦截属性变化,内部有个 Dep 实例用来依赖收集和触发更新。
- 依赖收集: 当你读取响应式数据时,会调用一个
trackRefValue
函数,这个函数会将当前正在执行的副作用(通常是effect
或computed
的 getter)注册为这个响应式数据的依赖。 - 触发更新: 当你修改响应式数据时,会调用
trigger
函数,这个函数会找到所有依赖这个数据的副作用,并通知它们执行更新。
- 依赖收集: 当你读取响应式数据时,会调用一个
- 还提供了一系列转换 ref 的工具函数。