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值。

image.png

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); // 返回数字 李四

总结

  1. 在setup钩子函数中调用 ref() 创建响应式变量。
  2. ref() 内部通过 createRef() 工厂函数执行返回一个值,如果值是 Ref 实例,直接返回该值,否则通过 RefImpl 构造函数创建一个实例,并返回该实例。
  3. RefImpl 是一个类构造函数,该构造函数主要通过 ES6 的 get/set 函数用来拦截属性变化,内部有个 Dep 实例用来依赖收集和触发更新。
    1. 依赖收集: 当你读取响应式数据时,会调用一个 trackRefValue 函数,这个函数会将当前正在执行的副作用(通常是 effectcomputed 的 getter)注册为这个响应式数据的依赖。
    2. 触发更新: 当你修改响应式数据时,会调用 trigger 函数,这个函数会找到所有依赖这个数据的副作用,并通知它们执行更新。
  4. 还提供了一系列转换 ref 的工具函数。