6-实现 isReactive & isReadonly

107 阅读2分钟

单侧

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

/*
 * @Author: Lin zefan
 * @Date: 2022-03-16 18:03:54
 * @LastEditTime: 2022-03-17 11:12:06
 * @LastEditors: Lin zefan
 * @Description:
 * @FilePath: \mini-vue3\src\reactivity\test\readonly.spec.ts
 *
 */

describe("readonly", () => {
  /** 检测readonly
   * 1. 是否为只读
   * 2. 检测是否为reactive、readonly对象
   */
  it("happy path", () => {
    const original = { foo: 1, bar: { baz: 2 } };
    const state = reactive({
      foo: 1,
    });
    const wrapped = readonly(original);
    wrapped.foo++;
    expect(wrapped.foo).toBe(1);
    expect(wrapped).not.toBe(original);
+    expect(isReactive(state)).toBe(true);
+    expect(isReadonly(wrapped)).toBe(true);
+    expect(isReadonly(original)).toBe(false);
  });

  /** 检测readonly内部调用set
   * 1. 调用Proxy set会输出一个console.warn
   * 2. 校验是否成功输出console.warn
   */
  it("warn then call set", () => {
    console.warn = jest.fn();

    const u = readonly({
      age: 10,
    });
    u.age = 11;

    expect(console.warn).toBeCalled();
  });
});

核心逻辑

isReactive

index.ts

/*
 * @Author: Lin zefan
 * @Date: 2022-03-15 13:08:22
 * @LastEditTime: 2022-03-17 11:10:26
 * @LastEditors: Lin zefan
 * @Description:
 * @FilePath: \mini-vue3\src\reactivity\index.ts
 *
 */

/* 看得见的思考
* 1. 判断是否为reactive对象,是不是可以借助一个固有的key来判断?
* 2. 我们知道,可以通过 obj[key] 来获取值,那是不是可以在set的时候,拦截一下?
*/

import { mutableHandles, readonlyHandles } from "./baseHandlers";

+ export const enum ReactiveEnum {
+   IS_REACTIVE = "__v_isReactive",
+ }

function createdBaseHandler(raw, baseHandler) {
  return new Proxy(raw, baseHandler);
}

export function reactive(raw) {
  return createdBaseHandler(raw, mutableHandles);
}

export function readonly(raw) {
  return createdBaseHandler(raw, readonlyHandles);
}

+ export function isReadonly(raw) {
+   // 双取反是为了兼容返回undefined
+   return !!raw[ReactiveEnum.IS_READONLY];
+ }


baseHandlers.ts

/*
 * @Author: Lin zefan
 * @Date: 2022-03-16 18:30:25
 * @LastEditTime: 2022-03-17 11:12:04
 * @LastEditors: Lin zefan
 * @Description:
 * @FilePath: \mini-vue3\src\reactivity\baseHandlers.ts
 *
 */
+ import { ReactiveEnum } from ".";
import { track, trigger } from "./effect";

function createdGetter(isReadonly = false) {
  return function (target, key, receiver) {
+    // 判断是否为reactive
+    if (key === ReactiveEnum.IS_REACTIVE) {
+      return !isReadonly;
+    }
    const res = Reflect.get(target, key, receiver);
    // 如果是readonly,不会进行收集
    !isReadonly && track(target, key);
    return res;
  };
}

function createdSetter() {
  return function (target, key, value, receiver) {
    const res = Reflect.set(target, key, value, receiver);
    trigger(target, key);
    return res;
  };
}

// 避免多次创建,这里直接用变量接收~
const get = createdGetter();
const set = createdSetter();
const readonlyGet = createdGetter(true);

// 可变动的
export const mutableHandles = {
  get,
  set,
};

// 只读的
export const readonlyHandles = {
  get: readonlyGet,
  set(target, key, value, receiver) {
    // 给一个警告
    console.warn(`${key}是只读的,因为被readonly包裹了`, target);
    return true;
  },
};

isReadonly

其实理解isReactive,会发现isReadonly是一模一样的!!直接开工

index.ts

export const enum ReactiveEnum {
  IS_REACTIVE = "__v_isReactive",
+  IS_READONLY = "__v_isReadonly",
}
+ export function isReadonly(raw) {
+   // 双取反是为了兼容返回undefined
+   return !!raw[ReactiveEnum.IS_READONLY];
+ }

baseHandlers.ts

function createdGetter(isReadonly = false) {
  return function (target, key, receiver) {
    console.log("获取的key", key);
    // 判断是否为reactive
    if (key === ReactiveEnum.IS_REACTIVE) {
      return !isReadonly;
    }
    // 判断是否为readonly
+    if (key === ReactiveEnum.IS_READONLY) {
+      return isReadonly;
+    }
    const res = Reflect.get(target, key, receiver);
    // 如果是readonly,不会进行收集
    !isReadonly && track(target, key);
    return res;
  };
}