【Vue3源码】第五章 实现 reactive 和 readonly 嵌套对象绑定响应式
【Vue3源码】第五章 实现 reactive 和 readonly 嵌套对象绑定响应式
前言
上一章节我们实现了isReadonly和isReactive两个API。
还记得第一章时说的proxy无法代理嵌套对象形成响应式的问题吗?这一章我们实现 reactive 和 readonly 嵌套对象转换功能,以及shallowReadonly 和isProxy几个简单的API。
1、实现reactive和readonly嵌套对象转换功能
单元测试代码
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)})it("nested readonly", () => {// not setconst original = { foo: 1, bar: { baz: 2 } };const wrapped = readonly(original)expect(wrapped).not.toBe(original)expect(wrapped.foo).toBe(1)expect(isReadonly(wrapped)).toBe(true)expect(isReadonly(original)).toBe(false)expect(isReadonly(wrapped.bar)).toBe(true)expect(isReadonly(original.bar)).toBe(false)});
那么怎么实现呢?
实现代码
原理很简单,我们触发get捕获器时,判断传入的内容是否是个object,是个object就继续判断是reactive对象还是readonly对象,最后再递归调用一下reactive函数或者readonly函数给他的嵌套对象也绑定代理响应式。
**
Reflect.get()
**方法与从 对象 (target[propertyKey]
) 中读取属性类似,但它是通过一个函数执行来操作的。
export function isObject (val){return val !== null && typeof val === 'object'
}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是不是objectif(isObject(res)) {// 如果是isReadonly 就递归调用readonly反之递归调用reactivereturn isReadonly ? readonly(res) : reactive(res)}if (!isReadonly) {track(target, key)}return res}
}
当然看下我们vue3的源码其实还有更多判断,比如symbol还有接下来马上要实现的shallow
好了reactive和readonly嵌套对象通过所有单元测试!
2、实现 shallowReadonly 功能
Vue官网描述
单元测试代码
新建一个shallowReadonly.spec.ts文件
import { isReadonly,isReactive, shallowReadonly, readonly } from "../reactive";describe("shallowReadonly", () => {test("should not make non-reactive properties reactive", () => {const props = shallowReadonly({ n: { foo: 1 } });const readonlyProps = readonly({ n: { foo: 1 } })expect(isReadonly(props)).toBe(true);//readonly深层的对象也是只读的expect(isReadonly(readonlyProps.n)).toBe(true);// shallowReadonly深层对象是可以set的expect(isReadonly(props.n)).toBe(false);// shallowReadonly深层对象不是响应式对象expect(isReactive(props.n)).toBe(false);});it("should call console.warn when set", () => {console.warn = jest.fn()const user = shallowReadonly({age: 10})user.age = 11expect(console.warn).toBeCalled()})
});
实现代码
原理也是非常简单
它和readonly()API唯一的区别就是只作用于浅层,不需要递归调用使嵌套对象readonly
那么我们就可以通过createGetter高阶函数,新增一个参数作为开关返回shallowReadOnly的get捕获器
进入baseHandlers.ts文件
// 把shallow开关打开
const shallowReadOnlyGet = createGetter(true,true)function createGetter(isReadonly = false, shallow = false) {return function get(target, key) {if (key === ReactiveFlags.IS_REACTIVE) {//get捕获器就返回true告诉调用者这是一个reactive对象return !isReadonly} else if (key === ReactiveFlags.IS_READONLY) {// 同理返回isReadonly证明是readonlyreturn isReadonly}const res = Reflect.get(target, key)//判断shallow,如果是shallow是true的话,我们直接返回res即可,就不用去递归调用readonly了if (shallow) {return res}if (isObject(res)) {return isReadonly ? readonly(res) : reactive(res)}if (!isReadonly) {track(target, key)}return res}
}// 导出shallowReadonly专属的shallowReadonlyHandlers
// shallowReadonlyHandlers和readonlyHandlers的set捕获器其实功能是一样的,所以只要更换get捕获器即可
export const shallowReadonlyHandlers = extend({},readonlyHandlers,{get:shallowReadOnlyGet
})
进入reactive.ts文件
我们引入shallowReadonlyHandlers去生成一个专属shallowReadonly的Proxy代理逻辑即可~
export const shallowReadonly = (raw) => {return createActiveObject(raw, shallowReadonlyHandlers)
}
shallowReadonly的单元测试也顺利通过~
3、实现 isProxy 功能
单元测试
reactive.spec.ts
it("happy path", () => {const originObj = { foo: 1 }const observed = reactive(originObj)expect(observed).not.toBe(originObj)expect(observed.foo).toBe(1)expect(isReactive(observed)).toBe(true)expect(isReactive(originObj)).toBe(false)//新增expect(isProxy(observed)).toBe(true)})
readonly.spec.ts
it("happy path", () => {// not setconst original = { foo: 1, bar: { baz: 2 } };const wrapped = readonly(original)expect(wrapped).not.toBe(original)expect(wrapped.foo).toBe(1)expect(isReadonly(wrapped)).toBe(true)expect(isReadonly(original)).toBe(false)expect(isReadonly(wrapped.bar)).toBe(true)expect(isReadonly(original.bar)).toBe(false)//新增expect(isProxy(wrapped)).toBe(true)});
代码实现
reactive.ts
export const isProxy = (value) => {//只要是其中之一就返回truereturn isReactive(value) || isReadonly(value)
}
通过本章的所有单元测试