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的实现如下:

js 复制代码
function ref(value) {
  return createRef(value, false);
}

ref的实现就是一个高阶函数,会返回调用createRef函数的结果。此时createRef接受的第二个参数shallowfalse,表示ref创建的响应式数据不是一个浅层响应。

createRef

createRef函数顾名思义就是创建一个响应式数据,其实现如下:

js 复制代码
function createRef(rawValue, shallow) {
  if (isRef(rawValue)) {
    return rawValue;
  }
  return new RefImpl(rawValue, shallow);
}

createRef函数接受两个参数,第一个参数rawValue表示原始数据,第二个参数shallow表示是否是浅层响应。首先会调用isRef(后面会讲到isRef的实现)判断原始数据rawValue是不是Ref响应式数据,若是,则直接返回该值;否则实例化RefImpl类,并返回实例对象。

RefImpl

RefImpl类的实现如下:

js 复制代码
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_isShallowtrue)或者新值的__v_isReadonly__v_isShallow属性为true,则直接使用新值,否则调用toRaw获取新值的原始值;然后调用hasChanged方法比较新值和实例的原始值是否相等,hasChanged的内部就是通过Object.is进行比较;若不等,则更新实例的_rawValue_value;最后调用triggerRefValue方法通知所有依赖于实例的副作用进行更新,该方法后续会介绍。

toRaw

toRaw的作用就是获取响应式数据的原始值,会返回参数observed__v_raw的值,若该值不存在,则返回参数observed

js 复制代码
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的实现如下:

js 复制代码
const toReactive = (value) => isObject(value) ? reactive(value) : value;

toReactive会判断参数是否是对象,若不是,则直接返回参数;否则,会调用reactive函数将参数转换为响应式对象,并返回该对象。所以若将对象作为参数传给ref,响应式实际上通过reactive函数实现的。

shallowRef

shallowRef的实现如下:

js 复制代码
function shallowRef(value) {
  return createRef(value, true);
}

shallowRef的实现和ref的实现类似,唯一的不同就是调用createRef时,第二个参数是true,表示创建的响应式数据是一个浅层响应式数据。

isRef

isRef的实现如下:

js 复制代码
function isRef(r) {
 return r ? r["__v_isRef"] === true : false;
}

isRef实际就是读取参数r__v_isRef属性,若该属性为true,则返回true,否则返回false。因为在创建ref响应式数据时,实例的__v_isRef都会设置为true

toRef

js 复制代码
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的实现如下:

js 复制代码
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_isReadonlytrue(表示是一个ref响应式数据且只读,因为实例没有setter的实现)

propertyToRef

propertyToRef的作用就是将对象的属性转换为ref响应式数据,它的实现如下:

js 复制代码
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的实现如下:

js 复制代码
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的作用就是将响应式数据转换为普通数据,它的实现如下:

js 复制代码
function toValue(source) {
  return isFunction(source) ? source() : unref(source);
}

toValue的参数source可以是一个getter函数或者响应式数据,若是函数,则调用该函数并返回函数的返回值;否则,调用unref函数并返回unref函数的返回值。

toRefs

toRefs的作用就是将响应式对象的所有属性转换为ref响应式数据,它的实现如下:

js 复制代码
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的实现如下:

js 复制代码
function unref(ref2) {
  return isRef(ref2) ? ref2.value : ref2;
}

unref会先调用isRef判断参数是否是响应式对象,若为响应式对象,则返回该对象的value属性;否则,直接返回参数。

proxyRef

proxyRef的相关实现如下

js 复制代码
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类,并返回该实例。

js 复制代码
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类接受一个工厂函数,该工厂函数接受两个参数函数:tracktrigger,分别用于收集依赖和触发更新,并且工厂函数还会返回一个对象,对象会包含getset的实现,在读取或设置自定义ref实例时,会分别调用getset。该类的实现给了用户更多的自主权去定制响应式行为。

triggerRef

triggerRef的实现如下:

js 复制代码
function triggerRef(ref2) {
  if (ref2.dep) {
    {
      ref2.dep.trigger({
        target: ref2,
        type: "set",
        key: "value",
        newValue: ref2._value
      });
    }
  }
}

triggerRef会调用triggerRefValue触发更新,triggerRefValue后续会介绍。