vue3 - mini-vue(九)实现isRef、unRef、proxyRef

182 阅读4分钟

isRef & unRef

isRef和unRef的实现都非常简单,就写在一起啦!

测试用例

it('isRef', () => {
    // 生命两个响应式数据 a 和 user
    const a = ref(1)
    const user = reactive({ a: 1 })
    // a 是ref
    expect(isRef(a)).toBe(true)
    // 1不是
    expect(isRef(1)).toBe(false)
    // user不是ref
    expect(isRef(user)).toBe(false)
  })

  it('unRef', () => {
    const a = ref(1)
    // 如果传入的值是ref,返回原始值
    expect(unRef(a)).toBe(1)
    // 如果是原始值,返回本身
    expect(unRef(1)).toBe(1)
  })

描述

isRef:用来判断传入的值是不是一个ref

unRef:如果传入的值是一个ref,返回它的value属性,否则返回自身

实现

/*
 * isRef和unRef的实现主要在于RefImpl类中新增的__v_isRef属性
 *   用它来判断是否是ref,或进行操作
 */
// 更新RefImpl
class RefImpl {
  ... // 省略
  // 增加一个属性写死的 __v_isRef属性,通过ref实例的 __v_isRef 属性判断它是不是一个ref
  public __v_isRef = true
  ... // 省略
}
// 判断传入的值是不是ref
export function isRef (ref) {
  // 如果传入的是一个原始数据类型,那么将返回undefined,所以通过取反两次将其转为布尔值
  return !!ref.__v_isRef
}

// 传入一个ref数据,返回它的原始值
export function unRef (ref) {
  // 如果是ref,返回它的value属性,否则直接返回就可以
  return isRef(ref) ? ref.value : ref
}

proxyRef

测试用例

it('proxyRef', () => {
    // 创建一个对象
    const user = {
      // age属性是一个ref
      age: ref(10),
      // name属性是基本类型数据
      name: 'lufei'
    }
    // 创建user的代理
    const proxyUser: any = proxyRef(user)
    // user.age.value === 10
    expect(user.age.value).toBe(10)
    // proxyUser.age === 10,省去了.value的操作
    expect(proxyUser.age).toBe(10)
    // proxyUser.name === 'lufei',与普通对象无异
    expect(proxyUser.name).toBe('lufei')
    // ---------上面测试的实际上是proxyUser读取值时的操作----------

    // 将要更新的是一个原始值
    proxyUser.age = 20
    // 更新后 proxyUser.age === 20,同读取一样,省去了.value的操作
    expect(proxyUser.age).toBe(20)
    // proxyUser更新后,相对于proxyUser的原始对象(也就是user)也会更新
    // user.age.value === 20
    expect(user.age.value).toBe(20)

    // 区别在于将要更新的值是一个ref
    proxyUser.age = ref(10)
    // 下面测试与上面同理
    expect(proxyUser.age).toBe(10)
    expect(user.age.value).toBe(10)
    // ---------上面测试的则是proxyUser设置值时的操作----------
  })

描述

代理ref,比如在vue模板(template)中使用ref时,不需要.value,直接使用就可以。

更新它的值时同时更新它的原始对象

因为proxyRef是用来代理ref的,且核心逻辑在get和set中,所以需要返回一个Proxy

根据测试用例不难发现:

get:读取proxyRef时,需要通过 Reflect.get(target, key) 来获取值,但获取到的值是一个ref,所以通过unRef获取到它的原始值

set:set共分为四种情况

  1. 当前的值是ref 且 将要更新的值不是ref的时候,需要给当前值(target[value])的value属性赋值

    否则直接给这个当前值取赋值就可以(这个重点在于当前值(target[key])是不是ref

  2. 两个都不是ref的情况下,简单赋值就可以:target[key] = value

  3. 在当前值不是一个ref,将要更新的值是ref,也是直接赋值:target[key] = value

  4. 在当前值(target[key])是一个ref,将要更新的值也是ref的情况下(两个值都是引用类型),需要修改的就是target[key]的指针,要做的操作也是:target[key] = value

实现

function proxyRef (objectWithRefs) {
  return new Proxy(objectWithRefs, {
    get (target, key) {
      return unRef(Reflect.get(target, key))
    },
    /*
    * set的时候判断它是不是ref类型,如果是ref类型,那么需要修改它的value属性
    * 否则
    * */
    set (target, key, value) {
      /*
      * 当前的值是ref 且 将要更新的值不是ref的时候,需要给当前值(target[value])的value属性赋值
      * 否则直接给这个当前值取赋值就可以(这个重点在于当前值(target[key])是不是ref,
      *   如果不是那么做简单的赋值操作就可以)
      *
      *   1.在当前值不是一个ref,将要更新的值是ref,也是直接赋值:target[key] = value
      *   2.两个都不是ref的情况下,简单赋值就可以:target[key] = value
      * 在当前值(target[key])是一个ref,将要更新的值也是ref的情况下(两个值都是引用类型),
      *     需要修改的就是target[key]的指针,要做的操作也是:target[key] = value
      * */
      if (isRef(target[key]) && !isRef(value)) {
        return target[key].value = value
      } else {
        return Reflect.set(target, key, value)
      }
    }
  })
}