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
})