手撸mini-vue之readonly

130 阅读1分钟

在 vue3 中 readonly 的作用是只读,下面通过一个单测来呈现这个功能

单测
// 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);
  });

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

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

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

readonly 不能 set,意味着不会触发依赖,不会触发依赖意味着不需要做依赖收集

实现代码
// reactive.ts
export function readonly(raw) {
  return new Proxy(raw, {
    get(target, key) {
      let res = Reflect.get(target, key);
      
      return res;
    },
    
    set(target, key, value) {
      console.warn(`key:${key} set 失败 因为 target 是 readonly`);
      return true;
    }
  })
}
代码重构

新建 baseHandlers.ts,存放 proxy handle的代码逻辑

// baseHandlers.ts

// 为了避免每次调用都创建一个 getter 或 setter
// 利用一个缓存的方法,先初始化 get,set,后续一直用初始化的get set 
const get = createGetter();
const set = createSetter();
const readonlyGet = createGetter(true);

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

    // 依赖收集
    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 readonlyHandlers = {
  get: readonlyGet,
  set(target, key, value) {
    console.warn(`key:${key} set 失败 因为 target 是 readonly`);

    return true;
  },
}
// reative.ts
import { mutableHandlers, readonlyHandlers } from "./baseHandlers";

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

export function readonly(raw: any) {
  return createActiveObject(raw, readonlyHandlers);
}

function createActiveObject(raw: any, baseHandle) {
  return new Proxy(raw, baseHandle);
}

源码地址戳这里