vue3 - mini-vue(六)实现reactive和readonly嵌套及shallowReadonly(表层readonly)

112 阅读3分钟

reactive和readonly嵌套

测试用例

// 测试reactive
describe('nested reactive', () => {
  const original = {
    nested: { foo: 1 },
    array: [{ bar: 2 }]
  }
  // 创建嵌套对象的代理
  const observed = reactive(original)
  // 代理对象的nested属性是reactive
  expect(isReactive(observed.nested)).toBe(true)
  // 代理对象的array属性是reactive
  expect(isReactive(observed.array)).toBe(true)
  // 代理对象的array属性的第一项是reactive
  expect(isReactive(observed.array[0])).toBe(true)
})

// 测试readonly
it('happy path', () => {
    const original = { foo: 1, bar: { baz: 2 } }
    const wrapped: any = readonly(original)
    expect(isReadOnly(wrapped.bar)).toBe(true)
    expect(isReadOnly(original)).toBe(false)
  })

描述

嵌套reactive:对于嵌套的代理对象,我们通过get方法读取它的某个值的时候,在返回之前判断以下这个值是不是

普通对象,如果是普通对象,则用reactive方法对这个普通对象进行一个包装并返回,那么就实现reactive嵌套了。

嵌套readonly:同理如果是普通对象,用readonly对其进行包装。

这里核心的逻辑在代理对象的 get 方法中,所以完善createGetter就行

实现

// 更新createGetter
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)
    /*
    * 检查res是不是 普通对象,如果是普通对象,使用reactive包装它并返回
    * readonly的嵌套也也是在这里实现,根据 isReadonly判断
    *   如果是 isReadonly === true 用readonly包装并返回,否则用reactive包装并返回
    *
    * 这块我自己有一个问题,就是在这里返回 res 下面收集依赖操作难道就不需要了吗?
    * 思考了下,答案是需要的,不过收集依赖并不是在这一层做的,因为它 res 是一个普通对象,
    *   所以在这里收集依赖是没有意义的,而且也收集不了,通过reactive包装之后,会在包装的这一层
    *   去收集依赖。
    * */
    if (isObject(res)) {
      // return reactive(res) 这一步是开始只实现了reactive嵌套
      return isReadonly ? readonly(res) : reactive(res)
    }
    if (!isReadonly) {
      track(target, key)
    }
    return res
  }
}


// 检查一个值是不是普通对象
function isObject (val) {
  return val !== null && typeof val === 'object'
}

shallowReadonly

测试用例

/*
* 只能是最外层的一层是响应式对象
* 即表层是一个响应式对象,内部呢就是普通对象
* */
describe('shallowReadonly', () => {
  test('should not make non-reactive properties readtive', () => {
    const props = shallowReadonly({ n: { foo: 1 } })
    expect(isReadOnly(props)).toBe(true)
    expect(isReadOnly(props.n)).toBe(false)
  })
  // 因为它也是 readonly的一种,所以set的时候也需要满足readonly的条件
  // 将readonly的单元测试搬过来稍微修改下
  it('warn then call set', () => {
    console.warn = jest.fn()
    const user = shallowReadonly({ age: 10 })
    user.age = 11
    expect(console.warn).toBeCalled()
  })
})

描述

shallowReadonly:表层的readonly,即不是嵌套的readonly,看测试用例的描述可以了解它的功能了。

实现

如果是shallowReadonly,调用get方法的时候,返回它的原始对象就可以。实现起来和readonly是一个思路。在createGetter的时候传入参数,表示它是否是一个shallowReadonly,如果是则直接返回res。

// 更新createGetter
function createGetter (isReadonly = false, shallow = 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)

    /*
    * 检查是不是shallowReadonly,如果是(shallowReadonly包含两个条件:1.不需要嵌套 2.是readonly,不需要收集依赖),
    *   则不需要进行包装,直接返回res就可以
    * */
    if (shallow) {
      return res
    }

    if (isObject(res)) {
      return isReadonly ? readonly(res) : reactive(res)
    }
    if (!isReadonly) {
      track(target, key)
    }
    return res
  }
}

// 创建shallowReadonly对象,实现起来和 reactive/readonly 一样
function shallowReadonly (raw) {
  return createActiveObject(raw, shallowReadonlyHandlers)
}

// 先调用 createGetter 传入参数得到一个 shallowReadonly 的 getter 方法
const shallowReadonlyGet = createGetter(true, true)

/*
 * 然后呢?因为它也是一个radonly,所以在set的时候也需要像普通的readonly一样发出警告。
 *   所以可以这样实现 shallowReadonlyHandlers
 * 将这三个对象合并,合并后得到的 shallowReadonlyHandlers 包含 readonly 的set方法和新创建的
 *   shallowReadonlyGet方法
 */
const shallowReadonlyHandlers = extend({}, readonlyHandles, {
  get: shallowReadonlyGet

})