Vue3 瞎搞第二蛋:实现 isReactive 对 reactive 数据的判断;实现 nested object 的代理...........

604 阅读3分钟

Vue3 瞎搞第二蛋:实现 isReactive 对 reactive 数据的判断;实现 nested object 的代理;实现 readonly 和 isReadonly;实现 shallowReactive 和 shallowReadonly

git地址:github.com/sundada88/s…

  1. 所有的对象可以通过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];
    }
    

    我们还需要在Proxyget的时候,当get属性__v_isReactive的时候,返回true

    function reactive(target) {
      //...省略代码
      get( target, key, receiver) {
        if (key === ReactiveFlags.IS_REACTIVE) return true
       // ... 省略
      }
    }
    
  2. 嵌套的对象的所有属性的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)
       // ... 省略
      }
    
  3. 实现 readonly: readonly就是实现对一个对象的readonly代理,这个代理对象不能对属性赋新值,所以不需要依赖收集,如果被赋值了会console出一个警告 添加一个测试文件readonly.spec.ts

    describe("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);
      });
    });
    

    因为readonlyreactive都需要使用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文件中需要导出mutableHandlersreadonlyHandlers两个变量

    // 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函数

  4. isReadonlyisProxy 的实现; 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);
    }
    
  5. 实现shallowReadonlyshallowReactive

    1. 增加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,
      };
      
    2. 增加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;
        },
      };
      
  6. 技术总结

    1. readonly通过改变Proxy中的getset来实现
    2. 嵌套的reactivereadonly对象通过判断返回的值是否是个object来进行递归
    3. shallow类型通过参数来判断是否继续递归还是返回原类型数据