likes
comments
collection
share

Vue3源码实现(二)—— 实现reactive、effect、依赖收集、触发依赖

作者站长头像
站长
· 阅读数 70

Vue3 reactive 分析

Vue3源码中,reactive 是通过Proxy实现的,所以平时我们在写代码的时候,把一个对象写到reactive里面会变成响应式,其实现机制就在于Proxygetset,如果是获取响应式对象的值,那么就会触发get方法,如果是设置值,那么就会触发set方法。

Vue3 effect 分析

先写一段测试代码看下

describe('effect', () => {
  it('测试 effect 触发', () => {
    const user = reactive({
      age: 10
    });

    let nextAge = 0;
    effect(() => {
      nextAge = user.age + 1;
    })

    expect(nextAge).toBe(11);

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

在这里,user对象用了reactive就会变成一个响应式对象。一开始初始化,给了一个属性age,并赋值10

接着,写了一个effect监听函数,其参数就是一个函数,且函数会立即执行,函数执行后,nextAge的值也就有了,就是 user.age + 1

注意这里,user.age + 1会发生什么事呢?这里user.age是获取值,也就会触发user这个响应式对象的get方法。

接着,我们再往下看那段测试代码。

user.age++; 
expect(nextAge).toBe(12);

user.age++说明user.age有变,那么就会触发set方法。

而在这里,nextAge依赖于user.age的变化,因为在effect的作用是,一旦user.age发生改变,那么nextAge的值也就会相应改变。

那么,问题就来了,这个effect是怎么实现监听到响应式对象的某个属性发生了变化,然后完成监听,触发相应函数执行的呢?

这就得说到依赖收集和触发依赖了。

依赖收集

在触发了响应式对象的get函数时,就需要在get函数内部实现依赖收集,把这个响应式对象、对象的key形成一个Map结构的对应关系

let targetMap = new Map();

let depsMap = targetMap.get(target);

if(!depsMap) {
    depsMap = targetMap.set(target, depsMap);
}

let dep = depsMap.get(key);

if(!dep) {
    dep = new Set();
    dep.set(key, dep);
}

dep.add(activeEffect);

activeEffect 是一个全局变量,下面会讲到它是啥。

触发依赖

在触发了响应式对象的set函数时,就需要在set函数内部实现触发依赖,让响应式数据在变化时,能及时触发调用,让依赖于这个响应式数据的数据发生改变,也就是能够再次执行一次effect函数。

那么在依赖收集和触发依赖之间,就要共同维护一个变量targetMap的变量,所以targetMap是一个全局变量。targetMap.get(target)获取到depsMap,然后根据发生了改变的key,定位找到依赖集const dep = depsMap.get(key),最终循环遍历依赖集,一一触发。

const depsMap = targetMap.get(target);
  const dep = depsMap.get(key);
  for(let effect of dep) {
    effect.run();
  }

dep是一些收集到的关于target对象中某个key的依赖,dep的每一个元素是一个表示当前effect的实例。

我把effect函数内部的实现写一下,应该会清晰点。

export function effect(fn: any) {
  let _effect = new ActiveEffect(fn);
  _effect.run();
}

effect是每执行一次,就实例化一个effect对象,所以每次执行effect函数都可以获得当前的effect实例。

再看run内部实现:

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

在执行run的时候,会把activeEffect赋值给当前effect实例对象:

activeEffect = this;

然后执行this._fn(),也就是effect函数的参数(一个函数),这会触发依赖收集,把当前的effect实例添加到key的dep依赖中。

dep.add(activeEffect);

也就是上面track函数那里为啥写了这句代码的原因。

关于reactive、effect、依赖收集、触发依赖的实现,上面已经分析完了。

下面附上完整的代码。

effect.ts

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

export function effect(fn: any) {
  let _effect = new ActiveEffect(fn);
  _effect.run();
}

let targetMap = new Map();
export function track(target: any, key: any) {
  // 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: any, key: any) {
  const depsMap = targetMap.get(target);
  const dep = depsMap.get(key);
  for(let effect of dep) {
    effect.run();
  }
}

reactive.ts

import { track, trigger } from './effect';

export function reactive(target: any) {
  return new Proxy(target, {
    get(target, key) {
      // const res = target[key];
      // 使用 Reflect.get
      const res = Reflect.get(target, key);

      // 依赖收集
      track(target, key);
      return res;
    },
    set(target, key, value) {

      // target[key] = value;
      Reflect.set(target, key, value);

      // 触发依赖
      trigger(target, key);

      return true;
    }
  })
}

下一次更新的内容是:实现 effect 返回 runner。

生命不息,奋斗不止

转载自:https://juejin.cn/post/7141787461461475365
评论
请登录