mini-vue3实现

115 阅读6分钟

趁过年,复习一波vue3,按照github上的教程来实现一个mini-vue3,目前还在实现响应式阶段

初始化目录结构

npm init -y
npm install typescript -D
npx tsc --init 
npm install jest @types/jest -D 

tsconfig设置配置

"types": ["jest"],
"noImplicitAny": false,
"lib": ["DOM","ES6"],

安装babel

npm install babel-jest @babel/core @babel/preset-env -D
npm install @babel/preset-typescript -D 

创建babel.config.js

module.exports = {
  presets: [["@babel/preset-env", { targets: { node: "current" } }]],
};

响应式实现

实现reactive和effect

测试用例

import { reactive } from "../reactive";
import { effect } from "../effect";
describe("effect", () => {
  it("happy path", () => {
    const user = reactive({
      age: 10,
    });
    let nextAge: any;
    effect(() => {
      nextAge = user.age + 1;
    });
    expect(nextAge).toBe(11);

    // update
    user.age++;
    expect(nextAge).toBe(12);
  });

  it("should return runner when effect is called",()=>{
    let foo = 10
    const runner = effect(()=>{
      foo++
      return 'foo'
    })
    // effect执行返回runner,调用runner会返回作为effect参数的函数的返回值
    expect(foo).toBe(11);
    const r = runner()
    expect(foo).toBe(12)
    expect(r).toBe('foo')
  })
});

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

实现reactive

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

export function reactive(raw) {
  return new Proxy(raw, {
    get(target, key) { 
      const res = Reflect.get(target, key);
      // 收集依赖
      track(target, key);
      return res;
    },
    set(target, key, value) {
      const res = Reflect.set(target, key, value);
      // 触发依赖
      trigger(target, key);
      return res;
    },
  });
}

实现effect、trigger和track

class ReactiveEffect {
  private _fn: any;
  constructor(fn) {
    this._fn = fn;
  }
  run() {
    activeEffect = this;
    return this._fn();
  }
}

// 收集依赖
const targetMap = new Map();
export function track(target, key) {
  // 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);
  }
  // 添加依赖
  dep.add(activeEffect);
}

// 触发依赖
export function trigger(target, key) {
  let depsMap = targetMap.get(target);
  let dep = depsMap.get(key);
  for (const effect of dep) {
    effect.run();
  }
}

let activeEffect; // 声明一个全局变量activeEffect,这样就能在track中获取到并push到dep中
export function effect(fn) {
  const _effect = new ReactiveEffect(fn);
  _effect.run();
  // 返回fn执行的结果
  return _effect.run.bind(_effect);
}

实现effect的scheduler

测试用例

// effect.spec.ts
it("scheduler", () => {
  // 1.通过effect的第二个参数 传入一个scheduler
  // 2.effect第一次执行的时候 会执行fn
  // 3.当响应式对象 更新的时候 不会执行fn 而是会执行scheduler
  // 4.当执行effect的返回值runner的时候,会执行fn
  let dummy;
  let run: any;
  const scheduler = jest.fn(() => {
    run = runner;
  });

  const obj = reactive({ foo: 1 });
  const runner = effect(
    () => {
      dummy = obj.foo;
    },
    { scheduler }
  );

  expect(scheduler).not.toHaveBeenCalled()
  expect(dummy).toBe(1)
  obj.foo++
  expect(scheduler).toHaveBeenCalledTimes(1)
  expect(dummy).toBe(1)
  run()
  expect(dummy).toBe(2)
});

代码实现

修改effect接受的参数

// effect.ts
class ReactiveEffect {
  private _fn: any;
  // scheduler添加public关键字 外部直接通过实例对象.scheduler来获取
  constructor(fn,public scheduler?) {
    this._fn = fn;
  }
  run() {
    activeEffect = this;
    return this._fn();
  }
}

let activeEffect; // 声明一个全局变量activeEffect,这样就能在track中获取到并push到dep中
export function effect(fn,options:any = {}) {
  const scheduler = options.scheduler
  const _effect = new ReactiveEffect(fn,scheduler);
  _effect.run();
  return _effect.run.bind(_effect);
}

在trigger函数中判断,是否传入scheduler,当响应式数值更新时,执行传入的scheduler

export function trigger(target, key) {
  let depsMap = targetMap.get(target);
  let dep = depsMap.get(key);
  for (const effect of dep) {
    if (effect.scheduler) {
      effect.scheduler();
    } else {
      effect.run();
    }
  }
}

实现effect的stop和onStop

测试用例

// effect.spec.ts
it("stop", () => {
  // 1.调用stop方法后,当数值更新,effect里的函数fn不执行,相当于清空依赖
  // 2.调用runner方法,当数值更新,effect里的函数fn执行,重新添加依赖
  let dummy;
  const obj = reactive({ prop: 1 });
  const runner = effect(() => {
    dummy = obj.prop;
  });
  obj.prop = 2;
  expect(dummy).toBe(2);
  stop(runner);
  obj.prop = 3;
  expect(dummy).toBe(2);

  runner();
  expect(dummy).toBe(3);
});

代码实现

编写stop方法,这里的runner是effect函数执行返回的runner

// effect.ts
export function stop(runner) {
  runner.effect.stop();
}
// effect.ts
let activeEffect: any; // 声明一个全局变量activeEffect,这样就能在track中获取到并push到dep中
export function effect(fn, options: any = {}) {
  const scheduler = options.scheduler;
  const _effect = new ReactiveEffect(fn, scheduler);
  _effect.run();
  const runner: any = _effect.run.bind(_effect);
  runner.effect = _effect; // 增加effect属性,保存实例,到时调用实例对象的stop方法清空依赖
  return runner;
}

ReactiveEffect上增加stop方法

// effect.ts
class ReactiveEffect {
  private _fn: any;
  deps = [];
  active = true;
  public scheduler: Function | undefined;

  constructor(fn, scheduler?) {
    this._fn = fn;
    this.scheduler = scheduler;
  }
  run() {
    activeEffect = this;
    return this._fn();
  }
  stop() {
    if (this.active) {
      cleanupEffect(this);
      this.active = false;
    }
  }
}

function cleanupEffect(effect) {
  effect.deps.forEach((dep: any) => {
    dep.delete(effect);
  });
  effect.deps.length = 0
}

实现effect的onStop方法

测试用例

// effect.spec.ts
it("onStop", () => {
  // 传入onStop,当调用stop方法时,传入的onStop会被执行
  const obj = reactive({
    foo: 1,
  });

  const onStop = jest.fn();
  let dummy;
  const runner = effect(
    () => {
      dummy = obj.foo;
    },
    {
      onStop,
    }
  );


  stop(runner)
  expect(onStop).toBeCalledTimes(1)

});

代码实现

修改effect函数

let activeEffect: any; // 声明一个全局变量activeEffect,这样就能在track中获取到并push到dep中
export function effect(fn, options: any = {}) {
  const scheduler = options.scheduler;
  const _effect = new ReactiveEffect(fn, scheduler);
  _effect.onStop = options.onStop // 增加一个onStop属性
  _effect.run();
  const runner: any = _effect.run.bind(_effect);
  runner.effect = _effect;
  return runner;
}
class ReactiveEffect {
  private _fn: any;
  deps = [];
  active = true;
  onStop?:()=>void
  public scheduler: Function | undefined;
  constructor(fn, scheduler?) {
    this._fn = fn;
    this.scheduler = scheduler;
  }
  run() {
    activeEffect = this;
    return this._fn();
  }
  stop() {
    if (this.active) {
      cleanupEffect(this);
      if(this.onStop){ // 如果传入了onStop方法 调用该方法
        this.onStop()
      }
      this.active = false;
    }
  }
}

实现readonly

测试用例

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

describe('readonly',()=>{
    it('happy path',()=>{
        const origin = {foo:1,bar:{baz:2}}
        const wrapped = readonly(origin)
        expect(wrapped).not.toBe(origin)
        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()
    })
})

代码实现

新建basehandler.ts

// basehandler.ts
import { track, trigger } from "./effect";

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;
  },
};

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

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

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

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

实现isReactive

测试用例

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);
    // 判断是否是响应式对象
    expect(isReactive(observed)).toBe(true)
    expect(isReactive(original)).toBe(false)
  }); 
});

代码实现

// reactive.ts
// 添加枚举类型
export const enum ReactiveFlags  { 
  IS_REACTIVE = '__v_isReactive'
}

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

判断一个对象是否是响应式,必然触发get操作,在createGetter中添加判断

// basehandler.ts
import { ReactiveFlags } from "./reactive";

function createGetter(isReadonly = false) {
  return function get(target, key) {
    if (key === ReactiveFlags.IS_REACTIVE) {
      return !isReadonly;
    }
    const res = Reflect.get(target, key);
    if (!isReadonly) {
      track(target, key);
    }
    return res;
  };
}

实现isReadonly

测试用例

// readonly.spec.ts
it('happy path',()=>{
    const origin = {foo:1,bar:{baz:2}}
    const wrapped = readonly(origin)
    expect(wrapped).not.toBe(origin)
    expect(isReadonly(wrapped)).toBe(true)
    expect(wrapped.foo).toBe(1)
})

代码实现

// reactive.ts
export const enum ReactiveFlags  { 
  IS_REACTIVE = '__v_isReactive',
  IS_READONLY = '__v_isReadonly'
}

export function isReadonly(value) {
  return !!value[ReactiveFlags.IS_READONLY];
}
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);
    if (!isReadonly) {
      track(target, key);
    }
    return res;
  };
}

优化effect的stop功能

测试用例

it("stop", () => {
  // 1.调用stop方法后,当数值更新,effect里的函数fn不执行
  // 2.调用runner方法,当数值更新,effect里的函数fn执行
  let dummy;
  const obj = reactive({ prop: 1 });
  const runner = effect(() => {
    dummy = obj.prop;
  });
  // 只触发set操作
  obj.prop = 2;
  expect(dummy).toBe(2);
  stop(runner);
  obj.prop = 3;
  expect(dummy).toBe(2);
  // 触发get、set操作 obj.prop = obj.prop + 1
  obj.prop++
  expect(dummy).toBe(2)
  runner();
  expect(dummy).toBe(4);
});

代码实现

用一个变量shouldTrack来控制是否收集依赖

// effect.ts
let activeEffect: any;
let shouldTrack; // 是否应该收集依赖

class ReactiveEffect {
  private _fn: any;
  deps = [];
  active = true;
  onStop?: () => void;
  public scheduler: Function | undefined;
  constructor(fn, scheduler?) {
    this._fn = fn;
    this.scheduler = scheduler;
  }
  run() {
    if (!this.active) {
      return this._fn();
    }
    // 此时应该收集依赖
    shouldTrack = true;
    activeEffect = this;
    // 执行_fn 会触发track
    const result = this._fn();
    shouldTrack = false;
    return result;
  }
  stop() {
    if (this.active) {
      cleanupEffect(this);
      if (this.onStop) {
        this.onStop();
      }
      this.active = false;
    }
  }
}

// 收集依赖
const targetMap = new Map();
export function track(target, key) {
  if (!activeEffect) return;
  // 控制是否收集依赖
  if (!shouldTrack) 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);
  }
  if (dep.has(activeEffect)) return;
  // 添加依赖
  dep.add(activeEffect);
  // 反向收集所有依赖到ReactiveEffect实例对象的deps属性上
  activeEffect.deps.push(dep);
}

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

嵌套的reactive和readonly对象

测试用例

// reactive.spec.ts
it("nested reactive", () => {
  const origin = {
    nested: {
      foo: 1,
    },
    array: [{ bar: 2 }],
  };
  const observed = reactive(origin);
  expect(isReactive(origin.nested)).toBe(true);
  expect(isReactive(origin.array)).toBe(true);
  expect(isReactive(origin.array[0])).toBe(true);
});
// readonly.spec.ts
it('happy path',()=>{
    const origin = {foo:1,bar:{baz:2}}
    const wrapped = readonly(origin)
    expect(wrapped).not.toBe(origin)
    expect(isReadonly(wrapped)).toBe(true)
    expect(isReadonly(wrapped.bar)).toBe(true)
    expect(isReadonly(wrapped.bar)).toBe(true)
    expect(isReadonly(origin)).toBe(false)
    expect(wrapped.foo).toBe(1)
})

代码实现

// basehandler.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;
    }
    const res = Reflect.get(target, key);
    if (isObject(res)) {
      return isReadonly ? readonly(res) : reactive(res);
    }
    if (!isReadonly) {
      track(target, key);
    }
    return res;
  };
}
// shared/index.ts
export function isObject(val) {
  return val !== null && typeof val === "object";
}