mini-vue3【六】:isReactive和isReadonly

112 阅读2分钟

isReactive

我们首先补一下测试

// reactivity\test\reactive.spec.ts
import { isReactive, reactive } from "../reactive";
describe("reactive", () => {
  it("happy path", () => {
    const original = { foo: 1 };
    const observed = reactive(original);
    expect(observed).not.toBe(original);
    expect(observed.foo).toBe(1);

    // 判断这个对象是否是 reactive 类型
    expect(isReactive(observed)).toBe(true);
    expect(isReactive(original)).toBe(false);
  });
});

我们通过之前的 createGetter 方法,这里的 get 操作中包含了 isReadonly 参数,通过这个参数就可以知道当前触发的 get 操作是什么类型

所以我们只需要想办法触发 get 操作就行了

// reactivity\reactive.ts
export function isReactive(value) {
  return value["is_reactive"];
}
// reactivity\baseHandlers.ts
function createGetter(isReadonly = false) {
  return function get(target, key) {
    if (key === "is_reactive") {
      return !isReadonly;
    }
		...
  };
}

此时单测会有一个报错:expect(isReactive(original)).toBe(false); 中,isReactive(original)undefined

这是因为 original 不是一个 reactive 对象,所以不会调用 get,这个原始对象上又没有 is_reactive 参数,所以会返回 undefined

我们只需要对 isReactive 函数返回的内容转换成 bool 值就行了

// reactivity\reactive.ts
export function isReactive(value) {
  return !!value["is_reactive"];
}

此时单测就通过了

优化代码

我们把 is_reactive 参数名抽离成一个枚举

// reactivity\reactive.ts
export const enum ReactiveFlags {
  IS_REACTIVE = "__v_isReactive",
}
export function isReactive(value) {
  return !!value[ReactiveFlags.IS_REACTIVE];
}
// reactivity\baseHandlers.ts
function createGetter(isReadonly = false) {
  return function get(target, key) {
    if (key === ReactiveFlags.IS_REACTIVE]) {
      return !isReadonly;
    }
		...
  };
}

isReadonly

isReactive 类似,我们先写测试

// readonly.spec.ts
describe("readonly", () => {
  it("happy path", () => {
    const 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);
  });
});

代码实现:

// reactivity\reactive.ts
export const enum ReactiveFlags {
  IS_REACTIVE = "__v_isReactive",
  IS_READONLY = "__v_isReadonly",
}
export function isReadonly(value) {
  return !!value[ReactiveFlags.IS_READONLY];
}
// reactivity\baseHandlers.ts
function createGetter(isReadonly = false) {
  return function get(target, key) {
    if (key === ReactiveFlags.IS_REACTIVE) {
      return !isReadonly;
    } else if (key === ReactiveFlags.IS_READONLY) {
      return isReadonly;
    }
		...
  };
}

全部代码:

reactive.spec.ts

import { isReactive, reactive } from "../reactive";

describe.skip("reactive", () => {
  it("happy path", () => {
    const original = { foo: 1 };
    const observed = reactive(original);
    expect(observed).not.toBe(original);
    expect(observed.foo).toBe(1);

    // 补一下测试
    expect(isReactive(observed)).toBe(true);
    // 会得到一个 undefined,因为没有触发 get
    expect(isReactive(original)).toBe(false);
  });
});

readonly.sepc.ts

import { isReadonly, readonly } from "../reactive";

describe.skip("readonly", () => {
  it("happy path", () => {
    const 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);
  });

  it("warn then call set", () => {
    console.warn = jest.fn();
    const user = readonly({
      age: 10,
    });
    user.age = 11;
    expect(console.warn).toBeCalled();
  });
});

reactive.ts

import { mutableHandlers, readonlyHanders } from "./baseHandlers";

export const enum ReactiveFlags {
  IS_REACTIVE = "__v_isReactive",
  IS_READONLY = "__v_isReadonly",
}

export function reactive(raw) {
  return createActiveObject(raw, mutableHandlers);
}

export function readonly(raw) {
  return createActiveObject(raw, readonlyHanders);
}

export function isReactive(value) {
  return !!value[ReactiveFlags.IS_REACTIVE];
}

export function isReadonly(value) {
  return !!value[ReactiveFlags.IS_READONLY];
}

function createActiveObject(raw, baseHandlers) {
  return new Proxy(raw, baseHandlers);
}

baseHandlers.ts

import { track, trigger } from "./effect";
import { ReactiveFlags } from "./reactive";

const get = createGetter();
const set = createSetter();
const readonlyGet = createGetter(true);

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

    // 通过isReadonly这个变量就可以知道当前触发的是什么类型
    if (!isReadonly) {
      track(target, key);
    }
    return res;
  };
}
function createSetter() {
  return function set(target, key, value) {
    const res = Reflect.set(target, key, value);

    trigger(target, key);
    return res;
  };
}

export const mutableHandlers = {
  get,
  set,
};

export const readonlyHanders = {
  get: readonlyGet,
  set(target, key, value) {
    console.warn("不能调用");
    return true;
  },
};