11、带你一步步实现vue3源码之reactive&readonly嵌套

141 阅读2分钟

reactive和readonly嵌套

之前,我们通过reactive创建的响应式对象都是一些简单的对象,并不存在内部属性的值为object或者array,下面我们一起来看下面这种情况该如何处理。

编写测试

通过reactive创建一个较为复杂的响应式对象,并且我们期望这个对象里面的子对象也是一个响应式对象。

// src/reactivity/tests/reactive.spec.ts
describe("reactive", () => {
  ...
  test("nested reactive", () => {
    const original = {
      nested: {
        foo: 1
      },
      array: [{bar: 2}]
    }
    const observed = reactive(original)
    expect(isReactive(observed.nested)).toBe(true)
    expect(isReactive(observed.array)).toBe(true)
    expect(isReactive(observed.array[0])).toBe(true)
  })
})

通过readonly创建一个较为复杂的只读响应式对象,并且我们期望这个对象里面的子对象也是只读的。

// src/reactivity/tests/reactive.spec.ts
describe("readonly", () => {
  it("happy path", () => {
    ...
    expect(isReadonly(wrapped.bar)).toBe(true)
    expect(isReadonly(original.bar)).toBe(false)
  })
})

就目前的代码,这个测试肯定是通不过的,因为我们并没有针对这种情况做任何处理。

编码

下面,通过处理响应式对象的get请求,判断返回的结果是否为object,如果是,我们在外面包一层reactive就可以了。

1、创建一个公共的isObject函数

// src/shared/index.ts
...
export const isObject = (val) => {
  return val !== null && typeof val === 'object'
}

2、在createGetter中进行判断

如果Reflect.get(target, key)的值是object,就返回调用reactive()的结果,这样就是一个响应式对象了。

// src/reactivity/baseHandlers.ts
import { isObject } from "../shared/index"
import { reactive, ReactiveFlags, readonly } from "./reactive"
...
function createGetter (isReadonly = false) {
  return function get (target, key) {
    ...
    if (isObject(res)) {
      return reactive(res)
    }
    ...
  }
}

这样,reactive的嵌套就已经完成了,接下来,再来处理readonly的嵌套,其实这两者逻辑类似。

3、处理readonly嵌套逻辑

在上面,我们已经对返回的值做了判断,如果是object我们就进行包裹,其实readonly也是如此,下面,我们对createGetter进行改写

// src/reactivity/baseHandlers.ts
function createGetter (isReadonly = false) {
  return function get (target, key) {
    ...
    if (isObject(res)) {
      return isReadonly ? readonly(res) : reactive(res)
    }
    ...
  }
}

总结

这一节,我们通过判断get返回值是否为object,并通过isReadonly来确定是否是只读,然后通过reactivereadonly来包裹返回。