手撸mini-vue之reactive和readonly嵌套及shallowReadonly

118 阅读1分钟

reactive 嵌套

单元测试
// reactive.spec.ts

describe("reactive", () => {
  it("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);
  });
});

想要实现嵌套 只需在 get 的时候 对类型为 object 的 res 再用 reactive 声明一下

代码实现
// shared/index.ts

export const isObject = (val) => {
  return val !== null && typeof val === "object";
}
// baseHandlers.ts

function createGetter(isReadonly = false) {
  return function get(target, key) {
    const res = Reflect.get(target, key);
    
    if (isObject(res)) {
      return reactive(res)
    }
    
    ...
  }
}

readonly 嵌套

单元测试
// readonly.spec.ts

describe("readonly", () => {
  it("happy path", () => {
    const original = { foo: 1, bar: { baz: 2 } };
    const wrapped = readonly(original);

    expect(isReadonly(wrapped.bar)).toBe(true);
    expect(isReadonly(original.bar)).toBe(false);
  });
})

实现思路与 reactive 嵌套类似

代码实现
// baseHandler.ts

function createGetter(isReadonly = false) {
  return function get(target, key) {
    const res = Reflect.get(target, key);
    
    if (isObject(res)) {
      return isReadonly ? readonly(res) : reactive(res);
    }
  }
}

shallowReadonly

单元测试
// shallowReadonly.spec.ts 

describe("shallowReadonly", () => {
  it("should not make non-reactive properties reactive", () => {
    const props = shallowReadonly({ n: { foo: 1 } });
    expect(isReadonly(props)).toBe(true);
    expect(isReadonly(props.n)).toBe(false);
  });

  it("warn then call set", () => {
    console.warn = jest.fn();

    const user = shallowReadonly({ age: 10 });

    user.age = 11;
    expect(console.warn).toBeCalled();
  });
});

shallowReadonly 的功能是使其内部的对象是个普通的对象,只有最外层的是 proxy

实现思路:在 get 的时候不用 readonly 重新包装 res,即声明一个变量判断是否是shallow,是的话直接 return res

代码实现
// baseHandlers.ts

const shallowReadonlyGet = createGetter(true, true)

function createGetter(isReadonly = false, isShallow = 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);
    
    // 如果是 shallow 直接 return
    if (isShallow) {
      return res;
    }

    // 看看 res 是不是 object
    if (isObject(res)) {
      return isReadonly ? readonly(res) : reactive(res);
    }

    // 依赖收集
    if (!isReadonly) {
      track(target, key);
    }
    return res;
  };
}

export const shallowReadonlyHandlers = extend({}, readonlyHandlers, { 
  get: shallowReadonlyGet,
});
// reactive.ts

export function shallowReadonly(raw: any) {
  return createActiveObject(raw, shallowReadonlyHandlers);
}

源码地址戳这里