Vue3源码reactivity响应式篇之Ref
概览
vue3中ref
用于将原始数据(如布尔值、数字、字符串等)包裹成为响应式数据。具体实现的文件参见packages\reactivity\src\ref.ts
与之相关的有如下api
ref
: 用于创建响应式数据,当数据发生变化时,会触发视图更新。shallowRef
: 用于创建浅层响应式数据,只有数据重新被赋值时,才会触发视图更新。isRef
: 用于判断一个数据是否是ref
响应式数据。toRef
: 用于将一个响应式对象的属性或某数据转换为ref
响应式数据。toValue
:toRefs
:将响应式对象的所有属性转换为ref
响应式数据unref
: 解绑响应式数据,返回原始数据proxyRefs
: 用于为包含ref的对象创建代理customRef
: 自定义ref,用于创建一个自定义的ref响应式数据triggerRef
:强制触发ref
的更新
源码分析
ref
ref
的实现如下:
function ref(value) {return createRef(value, false);
}
ref
的实现就是一个高阶函数,会返回调用createRef
函数的结果。此时createRef
接受的第二个参数shallow
为false
,表示ref
创建的响应式数据不是一个浅层响应。
createRef
createRef
函数顾名思义就是创建一个响应式数据,其实现如下:
function createRef(rawValue, shallow) {if (isRef(rawValue)) {return rawValue;}return new RefImpl(rawValue, shallow);
}
createRef
函数接受两个参数,第一个参数rawValue
表示原始数据,第二个参数shallow
表示是否是浅层响应。首先会调用isRef
(后面会讲到isRef
的实现)判断原始数据rawValue
是不是Ref
响应式数据,若是,则直接返回该值;否则实例化RefImpl
类,并返回实例对象。
RefImpl
RefImpl
类的实现如下:
class RefImpl {constructor(value, __v_isShallow) {this.__v_isShallow = __v_isShallow; //是否是浅层响应的标志位__publicField(this, "_value"); // 响应式数据的当前值__publicField(this, "_rawValue"); // 原始数据__publicField(this, "dep",new Dep()); // 依赖收集器__publicField(this, "__v_isRef", true); // 标志位,用于判断是否是 Ref 响应式数据this._rawValue = __v_isShallow ? value : toRaw(value);this._value = __v_isShallow ? value : toReactive(value);}get value() {{this.dep.track({target: this,type: "get",key: "value"});}return this._value;}set value(newVal) {const oldValue = this._rawValue;const useDirectValue = this["__v_isShallow"] || isShallow(newValue) || isReadonly(newValue);newValue = useDirectValue ? newValue : toRaw(newValue);if (hasChanged(newValue, oldValue)) {this._rawValue = newValue;this._value = useDirectValue ? newValue : toReactive(newValue);{this.dep.trigger({target: this,type: "set",key: "value",newValue,oldValue});}}}
}
RefImpl
类在构造函数中会根据参数__v_isShallow
判断是否是浅层响应,若是,则直接将参数value
赋值给实例的_rawValue
和_value
;否则,会调用toRaw
函数将原始数据转换为原始值,再调用toReactive
函数将原始值转换为响应式数据,最后将转换后的数据赋值给实例的_rawValue
和_value
。
RefImpl
类还定义了get value()
和set value(newVal)
方法,用于获取和设置响应式数据的值。
在
get value()
方法中,会调用trackRefValue
函数进行依赖收集,然后返回实例的_value
属性;
在set value(newVal)
方法中,会先判断是否直接使用新值,若当前实例是浅层响应(__v_isShallow
为true
)或者新值的__v_isReadonly
或__v_isShallow
属性为true
,则直接使用新值,否则调用toRaw
获取新值的原始值;然后调用hasChanged
方法比较新值和实例的原始值是否相等,hasChanged
的内部就是通过Object.is
进行比较;若不等,则更新实例的_rawValue
和_value
;最后调用triggerRefValue
方法通知所有依赖于实例的副作用进行更新,该方法后续会介绍。
toRaw
toRaw
的作用就是获取响应式数据的原始值,会返回参数observed
的__v_raw
的值,若该值不存在,则返回参数observed
。
function toRaw(observed) {const raw = observed && observed["__v_raw"];return raw ? toRaw(raw) : observed;
}
toRaw
实际上是个递归函数,直到返回原始值为止。对于reactive
响应式数据而言,读取它的__v_raw
,会返回原始值,在BaseReactiveHandler
类中的get
实现的。
toReactive
前面提到ref
会将原始数据转换为响应式数据,而如果ref
接受的是一个对象,则返回的也是响应式对象,这就是在toReactive
中实现的。
toReactive
的实现如下:
const toReactive = (value) => isObject(value) ? reactive(value) : value;
toReactive
会判断参数是否是对象,若不是,则直接返回参数;否则,会调用reactive
函数将参数转换为响应式对象,并返回该对象。所以若将对象作为参数传给ref
,响应式实际上通过reactive
函数实现的。
shallowRef
shallowRef
的实现如下:
function shallowRef(value) {return createRef(value, true);
}
shallowRef
的实现和ref
的实现类似,唯一的不同就是调用createRef
时,第二个参数是true
,表示创建的响应式数据是一个浅层响应式数据。
isRef
isRef
的实现如下:
function isRef(r) {return r ? r["__v_isRef"] === true : false;
}
isRef
实际就是读取参数r
的__v_isRef
属性,若该属性为true
,则返回true
,否则返回false
。因为在创建ref
响应式数据时,实例的__v_isRef
都会设置为true
toRef
function toRef(source, key, defaultValue) {if (isRef(source)) {return source;} else if (isFunction(source)) {return new GetterRefImpl(source);} else if (isObject(source) && arguments.length > 1) {return propertyToRef(source, key, defaultValue);} else {return ref(source);}
}
toRef
的源码实现实际上是用了typescript的重载,参数会有以下几种情况:
- 第一个参数是
ref
响应式数据,直接返回该数据 - 第一个参数是函数,返回一个
GetterRefImpl
实例 - 第一个参数是对象,第二个参数是属性名,返回一个
PropertyRefImpl
实例 - 其他情况,返回一个
RefImpl
实例
GetterRefImpl
GetterRefImpl
的实现如下:
class GetterRefImpl {constructor(_getter) {this._getter = _getter;__publicField(this, "__v_isRef", true);__publicField(this, "__v_isReadonly", true);__publicField$1(this, "_value");}get value() {return this._value = this._getter();}
}
_getter
实际上就是toRef
中的第一个参数,它是一个函数,用于获取响应式数据的值,除此之外GetterRefImpl
类还会在构造函数中设置实例__v_isRef
和__v_isReadonly
为true
(表示是一个ref
响应式数据且只读,因为实例没有setter
的实现)
propertyToRef
propertyToRef
的作用就是将对象的属性转换为ref
响应式数据,它的实现如下:
function propertyToRef(source, key, defaultValue) {const val = source[key];return isRef(val) ? val : new ObjectRefImpl(source, key, defaultValue);
}
propertyToRef
接受三个参数:对象source
、属性名key
和默认值defaultValue
;会先通过isRef
判断值是否是响应式数据,若是,则直接返回;否则会实例化ObjectRefImpl
类,并返回实例。
ObjectRefImpl
ObjectRefImpl
的实现如下:
class ObjectRefImpl {constructor(_object, _key, _defaultValue) {this._object = _object;this._key = _key;this._defaultValue = _defaultValue;__publicField(this, "__v_isRef", true);__publicField$1(this, "_value");}get value() {const val = this._object[this._key];return this._value = val === void 0 ? this._defaultValue : val;}set value(newVal) {this._object[this._key] = newVal;}get dep() {return getDepFromReactive(toRaw(this._object), this._key);}
}
ObjectRefImpl
类用于将响应式对象的一个属性作为独立的ref
使用。
toValue
toValue
的作用就是将响应式数据转换为普通数据,它的实现如下:
function toValue(source) {return isFunction(source) ? source() : unref(source);
}
toValue
的参数source
可以是一个getter
函数或者响应式数据,若是函数,则调用该函数并返回函数的返回值;否则,调用unref
函数并返回unref
函数的返回值。
toRefs
toRefs
的作用就是将响应式对象的所有属性转换为ref
响应式数据,它的实现如下:
function toRefs(object) {const ret = isArray(object) ? new Array(object.length) : {};for (const key in object) {ret[key] = propertyToRef(object, key);}return ret;
}
toRefs
的参数object
可以是一个数组或者对象,若为数组,则返回一个数组,数组的每个元素都是一个ref
响应式数据;若为对象,则返回一个对象,对象的每个属性都是一个ref
响应式数据。
unref
unref
的实现如下:
function unref(ref2) {return isRef(ref2) ? ref2.value : ref2;
}
unref
会先调用isRef
判断参数是否是响应式对象,若为响应式对象,则返回该对象的value
属性;否则,直接返回参数。
proxyRef
proxyRef
的相关实现如下
const shallowUnwrapHandlers = {// 当访问代理对象的属性时会触发,使用`Reflect.get`获取属性值,再使用`unref`解包get: (target, key, receiver) => key === "__v_raw" ? target : unref(Reflect.get(target, key, receiver)),// 当设置代理对象的属性时会触发set: (target, key, value, receiver) => {const oldValue = target[key];if (isRef(oldValue) && !isRef(value)) {// 若旧值是ref响应式数据,且新值不是ref响应式数据,则直接设置旧值的value属性为新值oldValue.value = value;return true;} else {// 若旧值不是ref响应式数据,或新值是ref响应式数据,则直接调用`Reflect.set`设置属性值return Reflect.set(target, key, value, receiver);}}
};
function proxyRefs(objectWithRefs) {return isReactive(objectWithRefs) ? objectWithRefs : new Proxy(objectWithRefs, shallowUnwrapHandlers);
}
proxyRef
会先判断传入对象是否是响应式的,若是,则直接返回该对象;否则使用shallowUnwrapHandlers
作为处理器创建一个新的Proxy
代理对象并返回。
customRef
customRef
的本质上就是实例化一个customRefImpl
类,并返回该实例。
function customRef(factory) {return new CustomRefImpl(factory);
}class CustomRefImpl {constructor(factory) {__publicField$1(this, "dep");__publicField$1(this, "_get");__publicField$1(this, "_set");__publicField$1(this, "__v_isRef", true);__publicField$1(this, "_value");const dep = this.dep = new Dep();const { get, set } = factory(dep.track.bind(dep), dep.trigger.bind(dep));this._get = get;this._set = set;}get value() {return this._value = this._get();}set value(newVal) {this._set(newVal);}
}
customRefImpl
类接受一个工厂函数,该工厂函数接受两个参数函数:track
和trigger
,分别用于收集依赖和触发更新,并且工厂函数还会返回一个对象,对象会包含get
和set
的实现,在读取或设置自定义ref
实例时,会分别调用get
和set
。该类的实现给了用户更多的自主权去定制响应式行为。
triggerRef
triggerRef
的实现如下:
function triggerRef(ref2) {if (ref2.dep) {{ref2.dep.trigger({target: ref2,type: "set",key: "value",newValue: ref2._value});}}
}
triggerRef
会调用triggerRefValue
触发更新,triggerRefValue
后续会介绍。