手写Vue系列-实现effect、reactive、依赖收集、依赖触发

874 阅读1分钟

这篇文章主要讲述vue的响应式原理。

准备工作

为了让typescript支持es6语法,需要将DOMES6引入。

打开tsconfig.json,添加如下配置项:

{
    "lib": ["DOM", "ES6"]
}

编写reactive代码

创建src/reactivity/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
    }
  })
}

编写测试用例:src/reactivity/tests/reactive.spec.ts

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

编写effect代码

创建src/reactivity/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
  // const dep = new Set()
  let depsMap = targetMap.get(target);
  // 如果没有depsMap就创建一个
  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()
}

编写测试用例:src/reactivity/tests/effect.spec.ts

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