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);
}
2
3
ref的实现就是一个高阶函数,会返回调用createRef函数的结果。此时createRef接受的第二个参数shallow为false,表示ref创建的响应式数据不是一个浅层响应。
# createRef
createRef函数顾名思义就是创建一个响应式数据,其实现如下:
function createRef(rawValue, shallow) {
if (isRef(rawValue)) {
return rawValue;
}
return new RefImpl(rawValue, shallow);
}
2
3
4
5
6
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
});
}
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
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;
}
2
3
4
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);
}
2
3
shallowRef的实现和ref的实现类似,唯一的不同就是调用createRef时,第二个参数是true,表示创建的响应式数据是一个浅层响应式数据。
# isRef
isRef的实现如下:
function isRef(r) {
return r ? r["__v_isRef"] === true : false;
}
2
3
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);
}
}
2
3
4
5
6
7
8
9
10
11
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();
}
}
2
3
4
5
6
7
8
9
10
11
_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);
}
2
3
4
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);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
ObjectRefImpl类用于将响应式对象的一个属性作为独立的ref使用。
# toValue
toValue的作用就是将响应式数据转换为普通数据,它的实现如下:
function toValue(source) {
return isFunction(source) ? source() : unref(source);
}
2
3
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;
}
2
3
4
5
6
7
toRefs的参数object可以是一个数组或者对象,若为数组,则返回一个数组,数组的每个元素都是一个ref响应式数据;若为对象,则返回一个对象,对象的每个属性都是一个ref响应式数据。
# unref
unref的实现如下:
function unref(ref2) {
return isRef(ref2) ? ref2.value : ref2;
}
2
3
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);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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
});
}
}
}
2
3
4
5
6
7
8
9
10
11
12
triggerRef会调用triggerRefValue触发更新,triggerRefValue后续会介绍。