【崔学社】MiNi-Vue 3.x @mini-vue/reactive 响应式

832 阅读4分钟

mini-vue 从0到1

我们使用TDD开发mini-vue

我们先看 effect.spec.ts 新建effect.spec.ts

it("reactive", () => {
  const user = reactive({ age: 10 });
  let nextage;
  effect(() => {
    nextage = user.age + 1;
  });
  expect(nextage).toBe(11);
  // update
  user.age += 1;
  expect(nextage).toBe(12);
});

1. 我们首先新建一个user 响应式对象

2. 然后我们通过一个effect 来收集 我们响应式的依赖 effect里面接受一个 fn函数

3. 初始化的时候我们会去调用这个 fn 函数 第一次 初始化就会执行这个fn函数

4. 我们的 fn 函数 里面 调用了 user.age 这样就会触发我们get操作 我们的get操作就是要去收集这个依赖

5. user 这个响应式 对象就会把 这个fn 给收集起来 当我们去修改了user.age 的值的时候我们触发了set操作 他会把之前我们所有get 收集的依赖 (收集的依赖就是这个fn)然后去调用这个 依赖 就是调用这个fn

我们先实现reactive,然后我们在看 reactive 的测试 新建reactive.spec.ts

describe("reactive", () => {
  it("happy path", () => {
    const orginal = { foo: 1 };
    const obversed = reactive(orginal);
    expect(obversed.foo).toBe(1);
    expect(obversed).not.toBe(orginal);
    expect(isReactive(obversed)).toBe(true);
    expect(isProxy(obversed)).toBe(true);
  });
 })

然后我们实现 reactive 我们新建reactive.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
        }
    })
}

这时候我们验证 reactive 中的测试 就可以执行了 接下来~~

我们 proxy 代理 reactive返回的对象 所以我们只需要在 get收集effect的fn 然后在 set的时候触发fn

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

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

我们通过创建一个类 来封装我们 对 收集依赖的所有操作 符合面向对象的方式 就是我们在调用fn的时候 我们去执行 他的run 方法 这时候 我们的测试在 不更新数据的时候是可以通过的

然后我们再去实现 更新操作 这时候我们就需要 收集依赖和 触发 依赖 收集依赖是每一个 reactive 中的每一个 key 发生改变的时候我们都需要去收集 与之 相对应的 一个 fn 依赖 因为我们修改的是对象的属性 所以我们 还需要考虑如何 能够 让一个 一个对象 对应 多个key 每个key 对应不同的fn 依赖 ,完整代码如下


class ReactiveEffect {
  private _fn:any;
  constructor(fn) {
    this._fn = fn
  }
  run() {
    // 我们执行当前的run 之后 我们 就需要 保存我们 依赖 利用我们的全局变量获取
    activeEffect = this  // 这个 this 值得 是我们当前实例对象 就是这个我们effect 传入的fn
    this._fn()
  }
}

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


export function reactive(raw) {
  return new Proxy(raw, {
      get(target, key){
          const res = Reflect.get(target, key)
          // TODO 收集依赖
          track(target, key)
          return res
      },
      set(target,key,value){
          const res = Reflect.set(target,key,value)
          // TODO 触发依赖
          trigger(target, key)
          return res
      }
  })
}
// 收集的依赖 存在 deps里面 我们 是需要获取 effect 传入的fn 所以我们可以创建一个全局变量 /
let activeEffect
// 我们 的所有 结构就是 我们 需要 一个 目标对象去存 整个 target target 对应每一个 key  
// 然后 key里面放着我们所有的要存的并且一一对应的依赖
const targetMap = new WeakMap()
export function track(target, key) {
  // targetMap 是我们存放的一个数据结构 我们还需要判断当一开始 目标对象存储的数据结构没有的时候 所以
  // const depsMap = targetMap.get(target)
  let depsMap = targetMap.get(target)
  if (!depsMap) {
    depsMap = new Map()
    targetMap.set(target, depsMap)
  }
  // deps 也是一样 我们没有 我们就需要创建
  let deps = depsMap.get(key)
  if (!deps) {
    deps = new Set()
    depsMap.set(key, deps)
    // 这时候我们就把所有的 对象 的key 的对应fn 的关系 一一对应起来
  }
  // 接下来我们就要对 我们不同的key 所产生的 对应依赖 fn 去存到整个 deps里面 那我们如何 才能把 当前 我们需要
  // 收集的依赖 存在 deps里面 我们 是需要获取 effect 传入的fn 所以我们可以创建一个全局变量 
  // 我们 创建所有的 依赖 fn 合集  因为依赖不可以重复 所以我们需要 创建一个set
  // const deps = new Set()
  deps.add(activeEffect)
}

export function tigger(target, key) {
  // 我们把我们之前 get 获取的所有fn 都取出来 去调用一下
  let depsMap = targetMap.get(target)
  let deps = depsMap.get(key)
  for(const dep of deps) {
    dep.run()
  }
}