Vue3 瞎搞第二蛋:实现 isReactive 对 reactive 数据的判断;实现 nested object 的代理;实现 readonly 和 isReadonly;实现 shallowReactive 和 shallowReadonly
git地址:github.com/sundada88/s…
-
所有的对象可以通过
isReactive来判断是否是一个reactive对象 在reactive.spec.ts文件中添加一下内容test("Object", () => { const original = { foo: 1, bar: { foo: 123, }, }; const observed = reactive(original); expect(observed).not.toBe(original); expect(isReactive(observed)).toBe(true); expect(isReactive(original)).toBe(false); });以为判断是否是一个
reactive对象的依据是这个对象有没有使用reactive包裹过,所以我们可以在get中添加一个字段用来表示是否是reactive对象,由于后续我们还查看isReadonly等,所以我们使用一个enum来定义这个属性,在reactive.ts中新建如下代码export const enum ReactiveFlags { SKIP = "__v_skip", IS_REACTIVE = "__v_isReactive", IS_READONLY = "__v_isReadonly", IS_SHALLOW = "__v_isShallow", RAW = "__v_raw", }那么我们判断
isReactive,我们可以访问__v_isReactive属性是否为真export function isReactive(value): boolean { return !!value[ReactiveFlags.IS_REACTIVE]; }我们还需要在
Proxy的get的时候,当get属性__v_isReactive的时候,返回truefunction reactive(target) { //...省略代码 get( target, key, receiver) { if (key === ReactiveFlags.IS_REACTIVE) return true // ... 省略 } } -
嵌套的对象的所有属性的
isReactive应该都是true添加一下测试expect(isReactive(observed.bar)).toBe(true);那我们需要在
get的时候对返回的数据判断,如果是一个object,那么我就就要递归使用reactive来包裹这个对象function reactive(target) { //...省略代码 get( target, key, receiver) { if (key === ReactiveFlags.IS_REACTIVE) return true const res = Reflect.get(target, key, receiver) if (isObject(res)) return reactive(res) // ... 省略 } -
实现
readonly:readonly就是实现对一个对象的readonly代理,这个代理对象不能对属性赋新值,所以不需要依赖收集,如果被赋值了会console出一个警告 添加一个测试文件readonly.spec.tsdescribe("readonly", () => { it("happy path", () => { const obj = readonly({ foo: 1 }); console.warn = jest.fn(); expect(obj.foo).toBe(1); obj.foo = 2; expect(console.warn).toBeCalledTimes(1); }); });因为
readonly和reactive都需要使用Proxy来进行代理,只是给不同的set,get... , 所以我们将他提取成一个函数import { mutableHandlers, readonlyHandlers } from "./baseHandlers"; export const enum ReactiveFlags { SKIP = "__v_skip", IS_REACTIVE = "__v_isReactive", IS_READONLY = "__v_isReadonly", IS_SHALLOW = "__v_isShallow", RAW = "__v_raw", } function createReactiveObject(target, isReactive, baseHandlers) { const proxy = new Proxy(target, baseHandlers); return proxy; } export function reactive(target) { return createReactiveObject(target, false, mutableHandlers); } export function readonly(target) { return createReactiveObject(target, true, readonlyHandlers); } export function isReactive(value) { return !!value[ReactiveFlags.IS_REACTIVE]; }我们共同的部分逻辑提取成
createReactiveObject函数,用来传入不同的target, isReactive, baseHandlers来返回不同类型的代理对象. 那么baseHandlers.ts文件中需要导出mutableHandlers和readonlyHandlers两个变量// reactive 需要的代理参数 export const mutableHandlers = { get() {}, set() {}, }; // readonly 需要的代理参数 export const readonlyHandlers = { get() {}, set() {}, };创建
createGetter函数,通过传入参数isReadonly来分辨是reactive, 还是readonly, 如果是readonly则不需要进行track过程,那么递归处理这个对象的时候,也需要判断相应的代理类型进行递归代理import { isObject } from "../../shared/src/index"; import { track, trigger } from "./effect"; import { reactive, ReactiveFlags, readonly } from "./reactive"; function createGetter(isReadonly) { return function get(target, key, receiver) { if (key === ReactiveFlags.IS_REACTIVE) return true; const res = Reflect.get(target, key, receiver); // TODO: track track(target, key); if (isObject(res)) return isReadonly ? readonly(res) : reactive(res); return res; }; } const get = createGetter(false); const readonlyGet = createGetter(true); const set = createSetter(); // reactive 需要的代理参数 export const mutableHandlers = { get, set, }; // readonly 需要的代理参数 export const readonlyHandlers = { get: readonlyGet, set(target, key) { console.warn( `because this is a readonly object, so ${key} can't be set` ); return true; }, };以上我们就完成了对部分代码的重构,因为实现了
readonly函数,那么我们顺路实现isReadonly函数和isProxy函数,以及更新一下isReactive函数 -
isReadonly和isProxy的实现;isReactive函数的更新 在readonly.spec.ts中添加it("happy path", () => { const obj = readonly({ foo: 1 }); console.warn = jest.fn(); expect(obj.foo).toBe(1); obj.foo = 2; expect(console.warn).toBeCalledTimes(1); expect(isReadonly(obj)).toBe(true); });更新
baseHandlers.ts文件function createGetter(isReadonly) { return function get(target, key, receiver) { // update if (key === ReactiveFlags.IS_REACTIVE) { return !isReadonly; } else if (key === ReactiveFlags.IS_READONLY) { // add return isReadonly; } const res = Reflect.get(target, key, receiver); // ....省略 }; }增加
isProxy的测试, 及isProxy指的是可能是isReactive,可能是isReadonly// reactive.ts // ... 省略 export function isProxy(value) { return isReactive(value) || isReadonly(value); } -
实现
shallowReadonly和shallowReactive-
增加
shallowReactive测试内容const shallowObj = shallowReactive(original); expect(isReactive(shallowObj)).toBe(true); expect(isReactive(shallowObj.bar)).toBe(false);在
reactive.ts中增加export function shallowReactive(target) { return createReactiveObject(target, false, shallowReactiveHandlers); }在
baseHandlers.ts中添加如下内容// shallow 判断是否是 shallow 的 function createGetter(isReadonly, shallow) { return function get(target, key, receiver) { // ... 省略 // TODO: track if (!isReadonly) { track(target, key); } // 新添加 if (shallow) return res; if (isObject(res)) return isReadonly ? readonly(res) : reactive(res); return res; }; } // new add const shallowReactiveGet = createGetter(false, true); export const shallowReactiveHandlers = { get: shallowReactiveGet, set, }; -
增加
shallowReadonly测试内容const shallowObj = shallowReadonly({ foo: 1, bar: { foo: 1, }, }); expect(isReadonly(obj)).toBe(true); expect(isReadonly(obj.bar)).toBe(false);在
reactive.ts中增加export function shallowReadonly(target) { return createReactiveObject(target, true, shallowReadonlyHandlers); }在
baseHandlers.ts中添加如下内容// add const shallowReadonlyGet = createGetter(true, true); // new add export const shallowReadonlyHandlers = { get: shallowReadonlyGet, set(target, key) { console.warn( `because this is a readonly object, so ${key} can't be set` ); return true; }, };
-
-
技术总结
readonly通过改变Proxy中的get和set来实现- 嵌套的
reactive和readonly对象通过判断返回的值是否是个object来进行递归 shallow类型通过参数来判断是否继续递归还是返回原类型数据