6.实现effect & reactive & 依赖收集 & 触发依赖

222 阅读2分钟

我们可以开始第一个测试的编写了。之前的测试可以删掉。在src\reactivity\tests下创建一个effect.spec.ts测试文件,输入以下内容:

describe('effect', () => {
  it.skip('happy path', () => {
    const user = reactive({
      age: 10
    });
    let nextAge;
    effect(() => {
      nextAge = user.age + 1;
    });
    expect(nextAge).toBe(11);
     //update
    user.age++;
    expect(nextAge).toBe(12)
  });
})

再创建一个reactive.spec.ts测试文件,编写reactive的简单测试

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

接着在src/reactivity下创建reactive.ts文件,简单输入内容

export function reactive(raw){}

并在reactive.spec.ts中进行导入。

import {reactive} from '../reactive'
describe('reactivity', () => {
  it('happy path', ()=> {
    const original =  {foo: 1};
    const observed = reactive(original); 
    expect(observed).not.toBe(original);
    expect(observed.foo).toBe(1);
  })
})

这个时候,我们可以进行简单单元测试,运行yarn test reactive,reactive写在test后为会自动匹配文件名。

yarn test reactive

会发现报错了

image.png 所以我们接下来是想办法让测试通过。 reactive是通过proxy进行拦截。修改reactive.ts文件内容

export function reactive(raw){
  return new Proxy(raw,{
    
  })
}

并修改tsconfig的lib,这样就可以继续编写reactive,没有Proxy报错了。

image.png 接着继续编写reafect.ts


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

再次进行单元测试,yarn test reactive

yarn rest reactive

测试通过。 image.png

继续返回effect.spec.ts,在里面导入reative

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

完成后,我们还需要完成effect,新建efect.ts文件。

class ReactiveEffect{
  constructor(fn){
    this._fn = fn;
  }
  run(){
    this._fn();
  }
}

export function effect(fn){
  const _effect = new ReactiveEffect(fn);
  _effect.run();
}

然后在effect.spec.ts文件中导入effect.

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

暂时先不管update部分(注释掉),运行测试,是可以测试通过的。接着我们完善update部分,这需要添加依赖收集。我们完善effect.ts部分代码

class ReactiveEffect{
  private _fn:any;
  constructor(fn){
    this._fn = fn;
  }
  run(){
    activeEffect = this;
    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);
  }
  let dep = depsMap.get(key);
  if(!dep){
    dep = new Set();
    depsMap.set(key,dep)
  }
  dep.add(activeEffect);
}

let activeEffect;
export function effect(fn){
  const _effect = new ReactiveEffect(fn);
  _effect.run();
}

然后可以在effect.spec.ts中进行断点调试,调试需要我们安装jest插件。在vscode插件市场中搜索jest即可。

image.png 接着,我们需要实现trigger.

class ReactiveEffect{
  private _fn:any;
  constructor(fn){
    this._fn = fn;
  }
  run(){
    activeEffect = this;
    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);
  }
  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;
export function effect(fn){
  const _effect = new ReactiveEffect(fn);
  _effect.run();
}

并修改effect.spec.ts,放开update部分

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

执行单元测试yarn test,通过。当然,也可以打断点,将整个流程串一下。

image.png

代码地址:github.com/zhangchongy…