mini-vue3-响应式实现

68 阅读4分钟

继续上一篇,实现ref和computed

实现shallowReadonly函数

测试用例

// shallowReadonly.spec.ts

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

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

// reactive.ts
import { mutableHandlers, readonlyHandlers, shallowReadonlyHandlers } from "./basehandler";

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

实现shallowReadonlyHandlers

// basehandler.ts
import { extend } from "../shared";

const shallowReadonlyGet = createGetter(true, true);

// 添加shallow变量 判断是否是shallowReadonly
function createGetter(isReadonly = false, shallow = 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);

    if (shallow) {
      return res;
    }
    if (isObject(res)) {
      return isReadonly ? readonly(res) : reactive(res);
    }
    if (!isReadonly) {
      track(target, key);
    }
    return res;
  };
}

export const shallowReadonlyHandlers = extend({}, readonlyHandlers, {
  get: shallowReadonlyGet,
});

实现isProxy

测试用例

检测是否对象是否是通过reactive或者readonly创造出来的proxy

// reactive.spec.ts
import { isReactive, reactive, isProxy } 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);
    expect(isReactive(observed)).toBe(true);
    expect(isReactive(original)).toBe(false);
    expect(isProxy(observed)).toBe(true);
  });
});
// readonly.spec.ts
import { readonly, isReadonly, isProxy } from "../reactive";

describe("readonly", () => {
  it("happy path", () => {
    const origin = { foo: 1, bar: { baz: 2 } };
    const wrapped = readonly(origin);
    // ...省略
    expect(isProxy(wrapped)).toBe(true);

  });
});

代码实现

// reactive.ts
export function isProxy(value) {
  return isReactive(value) || isReadonly(value);
}

实现ref

ref分开三个测试用例来逐步实现

测试用例1及代码实现

// ref.spec.ts
import { effect } from "../effect";
import { ref } from "../ref";

describe("ref", () => {
  it("happy path", () => {
    const a = ref(1);
    expect(a.value).toBe(1);
  });
});

对应的代码实现

// ref.ts
import { hasChanged, isObject } from "../shared";
import { isTracking, trackEffects, triggerEffects } from "./effect";
import { reactive } from "./reactive";

class RefImp {
  private _value: any;
  constructor(value) {
    this._value = value;
  }

  get value() {
    return this._value;
  }
}

export function ref(value) {
  return new RefImp(value);
}

测试用例2及代码实现

// ref.spec.ts
it("should be reactive", () => {
  const a = ref(1);
  let dummy;
  let calls = 0;
  effect(() => {
    calls++;
    dummy = a.value;
  });

  expect(calls).toBe(1);
  expect(dummy).toBe(1);
  a.value = 2;
  expect(calls).toBe(2);
  expect(dummy).toBe(2);
  // same value should not trigger
  a.value = 2;
  expect(calls).toBe(2);
  expect(dummy).toBe(2);
});

对应的代码实现

effect.ts中把收集依赖和触发依赖单独抽离成一个函数

// 收集依赖
const targetMap = new Map();
export function track(target, key) {
  if (!isTracking()) return;
  // target(代理的对象) → key → dep
  // 代理对象的依赖容器
  let depsMap = targetMap.get(target);
  if (!depsMap) {
    depsMap = new Map();
    targetMap.set(target, depsMap);
  }
  // desMap中某个代理对象某个属性所有的依赖
  let dep = depsMap.get(key);
  if (!dep) {
    dep = new Set();
    depsMap.set(key, dep);
  }
  trackEffects(dep);
}

export function isTracking() {
  return shouldTrack && activeEffect !== undefined;
}

// 收集依赖的部分单独抽离成一个函数
export function trackEffects(dep) {
  if (dep.has(activeEffect)) return;
  // 添加依赖
  dep.add(activeEffect);
  // 反向收集所有依赖到ReactiveEffect实例对象的deps属性上
  activeEffect.deps.push(dep);
}

// 触发依赖
export function trigger(target, key) {
  let depsMap = targetMap.get(target);
  let dep = depsMap.get(key);
  triggerEffects(dep);
}

// 触发依赖的部分单独抽离成函数
export function triggerEffects(dep) {
  for (const effect of dep) {
    if (effect.scheduler) {
      effect.scheduler();
    } else {
      effect.run();
    }
  }
}

// 最终的effect函数
export function effect(fn, options: any = {}) {
  const scheduler = options.scheduler;
  const _effect = new ReactiveEffect(fn, scheduler);
  extend(_effect, options);
  _effect.run();
  const runner: any = _effect.run.bind(_effect);
  runner.effect = _effect;
  return runner;
}
// shared.ts
// 判断两个值是否相等
export function hasChanged(v1, v2) {
  return !Object.is(v1, v2);
}
// ref.ts
import { hasChanged, isObject } from "../shared";
import { isTracking, trackEffects, triggerEffects } from "./effect";
import { reactive } from "./reactive";

class RefImp {
  private _value: any;
  public dep;
  constructor(value) {
    this._value = value
    this.dep = new Set();
  }

  get value() {
    // 收集依赖
    trackRefValue(this);
    return this._value;
  }

  set value(newValue) {
    // 对比的时候 用原始值和新值比较 不是和proxy对象比较
    if (!hasChanged(newValue, this._raw)) return;
    this._value = newValue
    // 更新完值之后触发依赖
    triggerEffects(this.dep);
  }
}

function trackRefValue(ref) {
  if (isTracking()) {
    trackEffects(ref.dep);
  }
}


export function ref(value) {
  return new RefImp(value);
}

测试用例3及代码实现

// ref.spec.ts
it("should make nested properties reactive", () => {
  const a = ref({
    count: 1,
  });

  let dummy;

  effect(() => {
    dummy = a.value.count;
  });

  expect(dummy).toBe(1);
  a.value.count = 2;
  expect(dummy).toBe(2);
});

对应的实现

import { hasChanged, isObject } from "../shared";
import { isTracking, trackEffects, triggerEffects } from "./effect";
import { reactive } from "./reactive";

class RefImp {
  private _value: any;
  private _raw: any;
  public dep;
  constructor(value) {
    // 保存没有处理过的原始值
    this._raw = value;
    // 看看value是不是对象 是的话就用reactive包裹
    this._value = convert(value);
    this.dep = new Set();
  }

  get value() {
    // 收集依赖
    trackRefValue(this);
    return this._value;
  }

  set value(newValue) {
    // 对比的时候 用原始值和新值比较 不是和proxy对象比较
    if (!hasChanged(newValue, this._raw)) return;
    this._raw = newValue;
    this._value = convert(newValue);
    // 更新完值之后触发依赖
    triggerEffects(this.dep);
  }
}

function trackRefValue(ref) {
  if (isTracking()) {
    trackEffects(ref.dep);
  }
}

function convert(value) {
  return isObject(value) ? reactive(value) : value;
}

export function ref(value) {
  return new RefImp(value);
}

实现isRef和unRef

测试用例

// ref.spec.ts
it("isRef", () => {
  const a = ref(1);
  const user = reactive({
    age: 1,
  });
  expect(isRef(a)).toBe(true);
  expect(isRef(1)).toBe(false);
  expect(isRef(user)).toBe(false);
});

it("unRef", () => {
  const a = ref(1);
  const user = reactive({
    age: 1,
  });
  expect(unRef(a)).toBe(1);
  expect(unRef(1)).toBe(1);
});

代码实现

// ref.ts
class RefImp {
  private _value: any;
  private _raw: any;
  public dep;
  public __v_isRef = true; // 添加一个属性判断是否是ref对象
  // 省略
}


export function isRef(val) {
  return !!val.__v_isRef;
}

export function unRef(val) {
  return isRef(val) ? val.value : val;
}

实现proxyRef

这个函数可以运用在template中,不用在写.value

测试用例

it("proxyRef",()=>{
  const user = {
    age:ref(10),
    name:"xiaohong"
  }

  const proxyUser = proxyRef(user)
  expect(user.age.value).toBe(10)
  expect(proxyUser.age).toBe(10)
  expect(proxyUser.name).toBe("xiaohong")
})

代码实现

export function proxyRef(objectWithRefs) {
  return new Proxy(objectWithRefs, {
    get(target, key) {
      return unRef(Reflect.get(target, key));
    },
    set(target, key, value) {
      if (isRef(target[key]) && !isRef(value)) {
        return (target[key].value = value);
      } else {
        return Reflect.set(target, key, value);
      }
    },
  });
}

实现computed

分步骤实现computed

测试用例1及代码实现

// computed.spec.ts
import { computed } from "../computed";
import { reactive } from "../reactive";

describe("computed", () => {
  it("happy path", () => {
    const user = reactive({
      age: 1,
    });

    const age = computed(() => {
      return user.age;
    });

    expect(age.value).toBe(1)
  });
});

对应实现

// computed.ts
class ComputedRef {
  private _getter: any;
  constructor(getter) {
    this._getter = getter;
  }

  get value() {
    return this._getter();
  }
}

export function computed(getter) {
  return new ComputedRef(getter);
}

测试用例2及代码实现

// computed.spec.ts
it("should compute lazily",()=>{
  const value = reactive({
      foo:1
  })

  const getter = jest.fn(()=>{
      return value.foo
  })

  const cValue = computed(getter)

  expect(getter).not.toHaveBeenCalled()

  expect(cValue.value).toBe(1)
  expect(getter).toHaveBeenCalledTimes(1)

  // should not compute again
  cValue.value
  expect(getter).toHaveBeenCalledTimes(1)

  // should compute when needed
  value.foo = 2
  expect(getter).toHaveBeenCalledTimes(1)
  expect(cValue.value).toBe(2)
  expect(getter).toHaveBeenCalledTimes(2)

  // should not compute
  cValue.value
  expect(getter).toHaveBeenCalledTimes(2)

})

对应代码实现

// computed.ts
import { ReactiveEffect } from "./effect";

class ComputedRef {
  private _getter: any;
  private _dirty = true;
  private _value: any;
  private _effect: any;
  constructor(getter) {
    this._getter = getter;
      // scheduler
    this._effect = new ReactiveEffect(getter, () => {
      if (!this._dirty) {
        this._dirty = true;
      }
    });
  }

  get value() {
    // 当依赖的响应式对象发生改变时 dirty设置为true
    if (this._dirty) {
      this._dirty = false;
      this._value = this._effect.run();
      return this._value;
    }
    return this._value;
  }
}

export function computed(getter) {
  return new ComputedRef(getter);
}