vue3-ref的简易实现(4)

82 阅读2分钟

proxy代理的目标必须是非原始值,所以reactive不支持原始值类型。所以我们需要将原始值类型进行包装。

ref 和 shallowRef

const count = ref(0)
effect(() => {
    console.log(count.value) // 0 , 1
})
count.value = 1

先实现基本的一个结构, 在 package/reactivity/src 新建 ref.ts 文件

class RefImpl {
  public _value;
  public __v_isRef = true;
  // _rawValue原始值, 后续可以用来做比对, _shallow 为 true 也是直接返回该值就可以
  // _shallow 是否浅处理
  constructor(public _rawValue, public _shallow) {
    
  }

  get value() {}
  set value(newValue) {}
}

function createRef(value, shallow) {
  return new RefImpl(value, shallow);
}

export function ref(value) {
  return createRef(value, false);
}
// 区别在于代理的是对象的时候, 修改对象的属性值不会触发更新
export function shallowRef(value) {
  return createRef(value, true);
}

接下来实现 RefImpl 中的内容

import { isReactive, reactive } from "./reactive";
import { trackEffects, triggerEffects } from "./effect";

function toReactive(value) {
    // 如果传进来的值是对象, 那么就返回一个 reactive 对象
    return isObject(value) ? reactive(value) : value
}

class RefImpl {
  public _value;
  public __v_isRef = true;
  public dep = new Set;
  // _rawValue原始值, 后续可以用来做比对, _shallow 为 true 也是直接返回该值就可以
  // _shallow 是否浅代理
  constructor(public _rawValue, public _shallow) {
    // 赋值, 如果是浅代理对象修改属性不会触发依赖
    this._value = _shallow ? _rawValue : toReactive(_rawValue)
  }

  get value() {
      // 收集依赖
      trackEffects(this.dep);
      return this._value
  }
  set value(newValue) {
      // 先判断新旧值是否一致
      if(newValue !== this._rawValue) {
          this._rawValue = newValue; // 新值变旧值, 下次对比继续用
      	  this._value = this._shallow ? newValue : toReactive(newValue);
          // 更新依赖
          triggerEffects(this.dep);
      }
  }
}

toRef 和 toRefs

将 reactive 对象转为单个ref, 因为一般的结构赋值会使 reactive 对象的属性失去响应式

const state = reactive({
  foo: 1,
  bar: 2
})

let {foo, bar} = state
foo = 2 // 无法触发依赖更新

// 都可以触发更新
let foo = toRef(state, 'foo')
foo.value = 2
let {foo, bar} = toRefs(state)
foo.value = 2

toRef(object, key, defaultValue?)

// bashHandle.ts
export const enum reactiveFlags {
  IS_REACTIVE = "__v_isReactive",
  IS_REF = "__v_isRef", // 新增
}

// ref.ts
import { reactiveFlags } from "./bashHandle";

// 判断是否是ref数据
export function isRef(r) {
  return !!(r && r[reactiveFlags.IS_REF]);
}

// 本质上是利用了原reactive对象, 获取设置属性的时候实际上是对原对象做处理
class ObjectRefImpl {
  public __v_isRef = true;
  constructor(public _object, public _key, public _defaultValue) {}
  get value() {
    let val = this._object[this._key];
    return val === undefined ? this._defaultValue : val;
  }
  set value(newValue) {
    this._object[this._key] = newValue;
  }
}
// object 就是 reactive对象
export function toRef(object, key, defaultValue?) {
  const value = object[key];
  return isRef(value) ? value : new ObjectRefImpl(object, key, defaultValue);
}

toRefs(object)

export function toRefs(object) {
  // 不是代理对象提示警告
  if (!isReactive(object)) {
    console.warn(
      `toRefs() expects a reactive object but received a plain one.`
    );
  }
  // 判断是对象还是数组
  let result = Array.isArray(object) ? new Array(object.length) : {};
  for (const key in object) {
    // 复制一个一模一样的对象, 但是值都是ref, 这样结构拿到的就是ref对象而不是原始数据了
    result[key] = toRef(object, key);
  }
  return result;
}