◆

Vue3 源码:ref 实现原理与 RefImpl 解析

2023-04-14 03:030

Ref 的实现相对比较简单:主要是 RefImpl 这个类,这个类中会有 _rawValue、_value、dep、__v_isRef 四个属性,并且有 get set,当我们创建一个 ref 时便会使用这个类,为什么我们在使用 ref 的时候,总是要 ref.value,就是因为使用 ref.value 我们才能触发 get value 去拿到我们想要的数据。

其中 RefImpl 这个类中的其他四个属性 rawValue、_value、dep、__v_isRef

  • rawValue 是用来保存用户传入的原始的数据值,在用户更新 value 数据时,我们需要将新的 value 与之前的原始 value 进行对比,判断是否要执行更新
  • _value 就是用户最终在 .value 后获取到的值,这个 _value 是需要经过转换的,因为如果用户传入的原始的值是一个对象,那么我们需要对这个原始的值进行一层 reactive 包裹,这样才能实现响应式
  • dep: ref 同样需要进行依赖收集,dep 就是用来保存 effect 中的 activeEffect 类的,为的也就是 set 时执行 activeEffect 中的 run 方法
  • __v_isRef: 用来标志当前对象是不是 ref 对象
import { isObject } from '@vue/shared';
import { trackEffects, triggerEffects, isTracking } from './effect';
import { createDep } from './dep';
import { reactive } from './reactive';

export class RefImpl {
  private _rawValue: any;
  private _value: any;
  public dep;
  public __v_isRef = true;

  constructor(value) {
    this._rawValue = value;
    // 看看value 是不是一个对象,如果是一个对象的话
    // 那么需要用 reactive 包裹一下
    this._value = convert(value);
    this.dep = createDep();
  }

  get value() {
    // 收集依赖
    trackRefValue(this);
    return this._value;
  }

  set value(newValue) {
    // 当新的值不等于老的值的话,
    // 那么才需要触发依赖
    if (hasChanged(newValue, this._rawValue)) {
      // 更新值
      this._value = convert(newValue);
      this._rawValue = newValue;
      // 触发依赖
      triggerRefValue(this);
    }
  }
}

export function ref(value) {
  return createRef(value);
}

function convert(value) {
  return isObject(value) ? reactive(value) : value;
}

function createRef(value) {
  const refImpl = new RefImpl(value);

  return refImpl;
}

export function triggerRefValue(ref) {
  triggerEffects(ref.dep);
}

export function trackRefValue(ref) {
  if (isTracking()) {
    trackEffects(ref.dep);
  }
}

// 这里没有处理 objectWithRefs 是 reactive 类型的时候
// TODO reactive 里面如果有 ref 类型的 key 的话, 那么也是不需要调用 ref.value 的
// (but 这个逻辑在 reactive 里面没有实现)
export function proxyRefs(objectWithRefs) {
  return new Proxy(objectWithRefs, shallowUnwrapHandlers);
}

// 把 ref 里面的值拿到
export function unRef(ref) {
  return isRef(ref) ? ref.value : ref;
}

// !!为了转换为 boolean 否则为 undefined
export function isRef(value) {
  return !!value.__v_isRef;
}

export function hasChanged(value, oldValue) {
  return !Object.is(value, oldValue);
}

模版中为什么不需要使用 .value,因为 vue 在模版中使用了如下这样一个函数

// 这里没有处理 objectWithRefs 是 reactive 类型的时候
// TODO reactive 里面如果有 ref 类型的 key 的话, 那么也是不需要调用 ref.value 的
// (but 这个逻辑在 reactive 里面没有实现)
export function proxyRefs(objectWithRefs) {
  return new Proxy(objectWithRefs, shallowUnwrapHandlers);
}
// 这个函数的目的是
// 帮助解构 ref
// 比如在 template 中使用 ref 的时候,直接使用就可以了
// 例如: const count = ref(0) -> 在 template 中使用的话 可以直接 count
// 解决方案就是通过 proxy 来对 ref 做处理
const shallowUnwrapHandlers = {
  get(target, key, receiver) {
    // 如果里面是一个 ref 类型的话,那么就返回 .value
    // 如果不是的话,那么直接返回value 就可以了
    return unRef(Reflect.get(target, key, receiver));
  },
  set(target, key, value, receiver) {
    const oldValue = target[key];
    if (isRef(oldValue) && !isRef(value)) {
      return (target[key].value = value);
    } else {
      return Reflect.set(target, key, value, receiver);
    }
  },
};
💬留言板0

还没有留言,来发表第一条吧~

PAGE LOAD / LIVEVue3 源码:ref 实现原理与 RefImpl 解析加载中