vue3 - mini-vue(四)实现isReactive和isReadonly

112 阅读2分钟

isReactive

测试用例 - 在reactive的基础上

describe('reactive', () => {
  it('happy path', () => {
    const original = { foo: 1 }
    const observed = reactive(original)
    expect(observed).not.toBe(original)
    expect(observed.foo).toBe(1)

    // 检查observed和original是不是响应式对象和原始对象
    // isReactive(observed) === true
    expect(isReactive(observed)).toBe(true)
    // isReactive(original) === false
    expect(isReactive(original)).toBe(false)

  })
})

功能描述

用来检查传入的对象是不是reactive,是则返回true,否则返回false

实现

先考虑下该怎么去实现,上一篇将代码进行了重构,抽出了createGetter方法,在调用createGetter的时候传入了一个参数用来标识这个对象是readonly还是reactive。可以根据这一点来检查是否是reactive对象。

所以呢检查创建这个getter时候传入的isReadonly的值就可以判断它是否是reactive对象。那么怎么获取这个isReadonly呢,前面说创建getter,这个getter在访问代理对象的时候会执行,那么就可以获取到这个isReadonly了。

如果不是代理对象呢?先实现上面的再说。

// 定义两个常量,表示访问的key值
const enum ReactiveFlags {
  IS_REACTIVE = '__v_isReactive',
  IS_READONLY = '__v_isReadonly'
}

// 步骤一:根据上面描述中说的,访问一个属性,调用它的get方法,接着看步骤二
function isReactive (value) {
  return value[ReactiveFlags.IS_REACTIVE]
}
// 更新getter
function createGetter (isReadonly = false) {
  return function get (target, key) {

    // 步骤二:调用它的get方法,就可以拿到外部函数的isReadonly
    // 如果访问的key是 is_reactive,就说明调用了isReactive
    if (key === ReactiveFlags.IS_REACTIVE) {
      // 如果isReadonly是false,就说明它是一个reactive
      return !isReadonly
    }

    const res = Reflect.get(target, key)
    if (!isReadonly) {
      track(target, key)
    }
    return res
  }
}

上面留了一个问题,如果是普通对象呢

如果是普通对象就不会调用get方法,value[ReactiveFlags.IS_REACTIVE]的值就是undefined,给它进行两次取反就可以。如果是普通对象 !!undefined = false

// 更新isReactive
function isReactive (value) {
  // value[ReactiveFlags.IS_REACTIVE] 如果value是一个原始对象,那么它不会去触发get方法,返回的就是undefined,所以取反两次转为布尔值
  return !!value[ReactiveFlags.IS_REACTIVE]
}

isReadonly

测试用例

describe('readonly', () => {
  it('happy path', () => {
    const original = { foo: 1, bar: { baz: 2 } }
    const wrapped: any = readonly(original)
    expect(wrapped).not.toBe(original)
    expect(wrapped.foo).toBe(1)

    // 在测试readonly的基础上
    expect(isReadOnly(wrapped)).toBe(true)
    expect(isReadOnly(original)).toBe(false)

  })
  it('warn then call set', () => {
    console.warn = jest.fn()
    const user = readonly({ age: 10 })
    user.age = 11
    expect(console.warn).toBeCalled()
  })
})

功能描述

用来检查传入的对象是不是readonly,是则返回true,否则返回false

实现

与isReactive同理,所以不再过多描述

export function isReadOnly (value) {
  return !!value[ReactiveFlags.IS_READONLY]
}
// 更新createGetter
function createGetter (isReadonly = false) {
  return function get (target, key) {

    // 如果访问的key是 is_readonly,就说明调用了isReadonly
    if (key === ReactiveFlags.IS_READONLY) {
      // 直接返回isReadonly就可以
      return isReadonly
    }

    const res = Reflect.get(target, key)
    if (!isReadonly) {
      track(target, key)
    }
    return res
  }
}

最终代码

// 不了解ts的同学们,将它们当作js中的两个常量即可
const enum ReactiveFlags {
  IS_REACTIVE = '__v_isReactive',
  IS_READONLY = '__v_isReadonly'
}
function isReactive (value) {
  return !!value[ReactiveFlags.IS_REACTIVE]
}

function isReadOnly (value) {
  return !!value[ReactiveFlags.IS_READONLY]
}
function createGetter (isReadonly = false) {
  return function get (target, key) {

    if (key === ReactiveFlags.IS_REACTIVE) {
      return !isReadonly
    } else if (key === ReactiveFlags.IS_READONLY) {
      return isReadonly
    }

    const res = Reflect.get(target, key)
    if (!isReadonly) {
      track(target, key)
    }
    return res
  }
}