vue3源码-二、响应式原理effect的实现

发布时间 2023-04-30 20:06:41作者: 楸枰~

effect实现

  1. 定义effect方法:

    export function effect(fn, options: any = {}) {
      // 创建响应式的effect
      const effect = createReactiveEffect(fn, options);
    
      // 默认会让effect先执行一次
      if (!options.lazy) {
        effect();
      }
      return effect;
    }
    
    let uid = 0;
    let activeEffect; // 存储当前的effect
    const effectStack = []; // 保存effect 保证依赖关系
    function createReactiveEffect(fn, options) {
      // 返回响应式的effect
      const effect = function reactiveEffect() {
        // 保证effect没有加入到effectStack中
        if (!effectStack.includes(effect)) {
          try {
            effectStack.push(effect);
            activeEffect = effect;
            return fn();
          } finally {
            effectStack.pop();
            activeEffect = effectStack[effectStack.length - 1];
          }
        }
      };
      effect.id = uid++; // 制作一个effect表示,用于区分effect
      effect._isEffect = true; // 用于表示这个是响应式effect
      effect.raw = fn; // 保留effect对应的原函数
      effect.options = options; // 在effect上保存用户属性
      return effect;
    }
    
  2. effect函数执行时,进行取值操作,让属性记住对应的effect函数。

    function createGetter(isReadonly = false, shallow = false) {
        return function get(target, key, receiver) {
          	// ...
            if (!isReadonly) { // effect函数执行时,进行取值操作,让属性记住对应的effect函数
                track(target, TrackOpTypes.GET, key);
            }
        }
    }
    

    需要构建track方法进行依赖收集

    // 可以拿到当前的effect
    const targetMap = new WeakMap();
    export function track(target, type, key) {
      // 此属性不需要收集,因为没有在effect使用
      if (activeEffect === undefined) {
        return;
      }
      let depsMap = targetMap.get(target);
      if (!depsMap) {
        targetMap.set(target, (depsMap = new Map()));
      }
      let dep = depsMap.get(key);
      if (!dep) {
        depsMap.set(key, (dep = new Set()));
      }
      if (!dep.has(activeEffect)) {
        dep.add(activeEffect);
       	activeEffect.deps.push(dep);
      }
    }
    
  3. trigger触发更新

    对新增属性和修改属性做分类,在setter里面

    function createSetter(shallow = false) {
        return function set(target, key, value, receiver) {
            const oldValue = target[key];
            const hadKey =
                isArray(target) && isIntegerKey(key) ? Number(key) < target.length : hasOwn(target, key);
            const result = Reflect.set(target, key, value, receiver);
            if (!hadKey) {
                // 新增属性
                trigger(target, TriggerOpTypes.ADD, key, value)
            } else if (hasChanged(value, oldValue)) {
                // 修改属性
                trigger(target, TriggerOpTypes.SET, key, value, oldValue)
            }
            return result;
        }
    }
    

    触发effect找到依次执行,trigger方法。

    // 找属性对应的effect让其执行(数组,对象)
    export function trigger(target, type, key?, newValue?, oldValue?) {
      // 如果这个属性没有,收集过依赖,那不需要做任何操作
      const depsMap = targetMap.get(target);
      if (!depsMap) return;
      // 所有的要执行的effect全部存放到一个新的集合中,最终一起执行
      const effects = new Set(); // 去重
      const add = (effectsToAdd) => {
        if (effectsToAdd) {
          effectsToAdd.forEach((effect) => effects.add(effect));
        }
      };
    
      // 1.看修改的是不是数组的长度,因为修改长度影响比较大
      if (key === "length" && isAarray(target)) {
        //   如果对应的长度有依赖收集需要更新
        depsMap.forEach((dep, key) => {
          // 如果更改的长度小于收集的索引,那么这个索引也需要触发effect重新执行
          if (key === "length" || key > newValue) {
            add(dep);
          }
        });
      } else {
        // 可能是对象
        // 这里是修改,不能是新增
        if (key !== undefined) {
          add(depsMap.get(key)); // 如果实现新增
        }
        //   如果修改数组中的某一个索引
        switch (type) {
          case TriggerOrTypes.ADD:
            if (isAarray(target) && isIntegerKey(key)) {
              add(depsMap.get("length"));
            }
        }
      }
      effects.forEach((effect: any) => effect());
    }