亲宝软件园·资讯

展开

vue3源码分析reactivity实现原理

猪猪爱前端 人气:0

引言

上一章中我们解析了createApp到底发生了什么? 本来我们应该继续向下解析mount方法的,但是后面很多地方涉及到了响应式的api也就是reactivity的api,所以我们必须要单独将这一章拎出来做单独的讲解。本章主要分析内容:

第一部分:简单版reactivity

//设置响应式对象
const proxy = reactive({a:1})
//当proxy.a发生变化的时候,调用effect中的回调函数
effect(()=>{
 console.log(proxy.a)
})
proxy.a++

下面我们先来设计一个方案实现这样的效果

reactiveMap = {
  [object Object]:{
     [key]:new Set()
  }
}

了解了整个设计流程我们开始书写代码:

(1).实现reactive和effect

1.当effect函数即将开始执行的时候设置全局变量

let activeEffect = null;
const reactiveMap = new WeakMap();
function effect(fn) {
  const reactEffect = new ReactiveEffect(fn);
  reactEffect.run();//第一次立刻执行副作用函数
}
//存放副作用的类
class ReactiveEffect {
  constructor(fn, scheduler) {
    this.fn = fn;
    this.scheduler = scheduler;//调度器
  }
  run() {
    try {
      //执行之前先修改activeEffect
      activeEffect = this;
      return this.fn();
    } finally {
      //执行完设置activeEffect为null
      activeEffect = null;
    }
  }
}

2.创建响应式函数reactive

function reactive(obj) {
  //获取值的时候收集依赖
  const getter = function (object, key, receiver) {
    const res = Reflect.get(object, key, receiver); //获取真实的值
    track(object, key, res);
    if (typeof res === "object") {
      return reactive(res);
    }
    return res;
  };
  //当设置值的时候触发依赖
  const setter = function (object, key, value, receiver) {
    const res = Reflect.set(object, key, value, receiver);
    trigger(object, key, value, res);
    return res;
  };
  const mutations = {
    get: getter,
    set: setter,
  };
  const proxy = new Proxy(obj, mutations);
  return proxy;
}

3.实现track和trigger函数

function track(object, key, oldValue) {
  //首先看看之前是否有这个对象的depsMap
  //如果没有表示是第一次收集创建一个new Map
  let depsMap = reactiveMap.get(object);
  if (!depsMap) {
    reactiveMap.set(object, (depsMap = new Map()));
  }
  //如果是第一次收集这个key,则创建一个新的dep依赖
  let dep = depsMap.get(key);
  if (!dep) {
    depsMap.set(key, (dep = new Set()));
  }
  找到这个target和key对应的依赖之后进行副作用收集
  trackEffects(dep);
}
function trackEffects(dep) {
  //因为设置的对象是响应式的所以只要
  //响应式对象改变都会收集,但是只有
  //在effect执行的时候activeEffect才有值
  //才能收集到依赖并且dep采用了集合防止
  //重复收集同一个依赖
  if (activeEffect) {
    dep.add(activeEffect);
  }
}
//当修改值的时候触发依赖函数
function trigger(object, key, newVal, oldVal) {
  const depsMap = reactiveMap.get(object);
  const dep = depsMap.get(key);//找到target和key对应的dep
  //执行依赖函数
  if (dep.size > 0) {
    for (const effect of dep) {
      if (effect.scheduler) {
        effect.scheduler();
      } else effect.run();
    }
  }
}

这就是reactivity最核心的逻辑,是不是觉得非常简单呢?目前我们代理的是对象,那如果我们代理的时候是一个值呢?那就要使用到ref,下面我们来写写极简版的ref实现吧!

(2).实现ref

function ref(value) {
  return createRef(value);
}
function createRef(value) {
  return new RefImpl(value);
}
class RefImpl {
  constructor(value) {
    this.__v_isRef = true;
    this._value = value;
  }
  get value() {
    track(this, "value");
    return this._value;
  }
  set value(value) {
    this._value = value;
    trigger(this, "value", value, this._value);
    return true;
  }
}

(3).实现computed

说到computed,他是如何实现的呢?我们先来说说他的要求,computed接受一个getter,必须有返回值,当内部收集到的响应式发生改变的时候我们去读取compute.value也会发生相应的变化,并且computed返回的对象也是响应式的例如:

//设置响应式
const proxy = reactive({ a: 1, b: { a: 1 } });
//设置计算属性
const comp = computed(() => {
  return proxy.a + 1;
});
effect(() => {
  console.log(comp.value);
});
//当proxy.a发生变化,读取comp的value也会发生变化,并且因为comp是响应式
//在effect中被收集了,所以当proxy.a发生变化也会导致effect中的函数执行
proxy.a++;

下面我们来看看他的实现

这里必须要说一个scheduler,ReactiveEffect接受两个参数如果有第二个参数,那么就不会调用run方法而是调用scheduler方法。

所以computed的实现原理就是,当执行computed这个函数的时候创建ComputedRefImpl,而构造器中会自动创建ReactiveEffet,这个时候会传递一个schduler,也就是说以后这个effect不会调用run方法而是调用schduler方法,我们只需要在在shcduler方法中设置dirty为true表示修改了值,然后在进行调度,通过comp.value收集到的依赖就可以了,这里的响应式其实有两个地方,第一个地方是computed内部有一个响应式,第二是comp本身也是响应式需要收集依赖,当computed内部响应式发生变化会导致this._effect.scheduler执行,那么dirty会设置为true,当comp.value在其他effect中的时候会触发track收集依赖,所以当computed内部响应式发生改变就会触发get时候收集到的effect。

class ComputedRefImpl {
  constructor(getter) {
    //调度器
    this._effect = new ReactiveEffect(getter, () => {
      if (!this._dirty) {
        this._dirty = true;
        //修改了getter中监听到的值
        //引起对dep内的更新
        for (const effect of this.dep) {
          if (effect.scheduler) {
            effect.scheduler();
          } else effect.run();
        }
      }
    });
    this.dep = new Set(); //依赖
    this._dirty = true; //是否需要更新
    this._value = null;
  }
  get value() {
    trackEffects(this.dep); //收集依赖
    if (this._dirty) {
      this._dirty = false;
      this._value = this._effect.run();
    }
    return this._value;
  }
}

最后我们来试试效果吧

const proxy = reactive({ a: 1, b: { a: 1 } });
const comp = computed(() => {
  return proxy.a + 1;
});
const proxyRef = ref(100);
effect(() => {
  console.log(proxy.b.a);
  console.log(comp.value);
});
effect(() => {
  console.log(proxyRef.value);
});
proxy.a++;
proxy.b.a++;
proxyRef.value++;
//log:1 2 100 1 3 2 3 101

好啦! 看了reactivity的建议版本实现,相信你已经基本了解了reactivety,我们开始分析源码吧!

第二部分:深入分析对于object、array的响应式代理

//深度代理
export function reactive(target) {
  //如果被代理的是readonly返回已经被readonly代理过的target
  if (isReadonly(target)) {
    return target;
  }
  return createReactiveObject(
    target,
    false,
    mutableHandlers,
    mutableCollectionHandlers,
    reactiveMap
  );
}
//只代理第一层
export function shallowReactive(target) {
  return createReactiveObject(
    target,
    false,
    shallowReactiveHandlers,
    shallowCollectionHandlers,
    shallowReactiveMap
  );
}
//代理只读属性
export function readonly(target) {
  return createReactiveObject(
    target,
    true,
    readonlyHandlers,
    readonlyCollectionHandlers,
    readonlyMap
  );
}
//只代理只读的第一层
export function shallowReadonly(target) {
  return createReactiveObject(
    target,
    true,
    shallowReadonlyHandlers,
    shallowReadonlyCollectionHandlers,
    shallowReadonlyMap
  );
}

我们发现这四个api本质都是调用了createReactiveObject,但是他们传递的参数是不同的,对于不同的代理handlers处理是不同的,而其中还有对于map set等的代理就需要使用到collectionHandlers,对于代理过的对象我们再次对这个对象进行代理是不必要的,需要reactiveMap进行缓存。已经代理过的对象读取缓存就可以了。

接下来我们深入createReactiveObject,先来看看源代码

export function createReactiveObject(
  target,
  isReadonly,
  baseHandlers,
  collectionHandlers,
  proxyMap
) {
  //不能够代理非对象
  if (!isObject(target)) {
    {
      console.warn(`value cannot be made reactive: ${String(target)}`);
    }
    return target;
  }
  //已经代理过的对象不需要在进行二次代理
  if (target[RAW] && !(isReadonly && target[IS_REACTIVE])) {
    return target;
  }
  //防止重复代理
  const existingProxy = proxyMap.get(target);
  if (existingProxy) {
    return existingProxy;
  }
  //获取当前被代理对象的类型
  //为0表示被代理对象为不可拓展对象
  //或者当前对象含有__v_skip属性
  //为1表示Array,Object类型用baseHandlers处理
  //为2表示map set weakMap weakSet 用collectionHandlers处理
  const targetType = getTargetType(target);
  //不可代理 返回原对象
  if (targetType === 0) {
    console.warn(`current target:${target} can not been proxy!`);
    return target;
  }
  //进行代理
  const proxy = new Proxy(
    target,
    //判断当前代理对象的类型,如果是array object采用baseHandlers
    //如果是map set weakMap weakSet采用collectionHandlers
    targetType === 2 ? collectionHandlers : baseHandlers
  );
  proxyMap.set(target, proxy);
  //返回代理成功的对象
  return proxy;
}
//如果对象带有__v_skip或则对象不可拓展则不可代理
//然后根据类型判断需要哪种函数进行代理
export function getTargetType(value) {
  return value[SKIP] || !Object.isExtensible(value)
    ? 0
    : targetTypeMap(toRawType(value));
}
//对截取的类型判断 如果是object array返回1
//如果是set map weakMap weakSet返回2
export function targetTypeMap(rawType) {
  switch (rawType) {
    case "Object":
    case "Array":
      return 1;
    case "Map":
    case "Set":
    case "WeakMap":
    case "WeakSet":
      return 2;
    default:
      return 0;
  }
}
//截取类型
export const toRawType = (value) => {
  //截取[object Object]中的"Object"
  return Object.prototype.toString.call(value).slice(8, -1);
};

本部分我们仅讨论对于object和array类型的代理,所以我们跳过collectionHandlers的实现,现在我们来看看baseHandlers,baseHandlers显然是根据shallow readonly不同传递的不同的handlers,其中包含:

//reactive的proxy handlers
//这个便是new Proxy()中的第二个参数,可以拦截get
//set deleteProperty has ownKeys等进行处理
const mutableHandlers = {
  get,
  set,
  deleteProperty,
  has,
  ownKeys,
};
//处理readonly的proxy handler
const readonlyHandlers = {
  get: readonlyGet,
  //对于readonly的handlers不需要set值
  //打印警告,但是不修改值
  set(target, key) {
    {
      warn(
        `Set operation on key "${String(key)}" failed: target is readonly.`,
        target
      );
    }
    return true;
  },
  //对于只读属性,不能删除值
  deleteProperty(target, key) {
    {
      warn(
        `Delete operation on key "${String(key)}" failed: target is readonly.`,
        target
      );
    }
    return true;
  },
};
//处理只代理第一层的proxy handler
const shallowReactiveHandlers = shared.extend({}, mutableHandlers, {
  get: shallowGet,
  set: shallowSet,
});
//处理只对第一层做只读代理的proxy handler
const shallowReadonlyHandlers = shared.extend({}, readonlyHandlers, {
  get: shallowReadonlyGet,
});
//这里的shared.extend就是Object的assign方法
//shared.extend = Object.assgin

显然,在以上代码中出现了几个代理函数分别是getter setter deleteProperty ownKeys has,接下来我们便对每一个进行分析。

(1).handlers中的getter

const get = createGetter();
const shallowGet = createGetter(false, true);
const readonlyGet = createGetter(true, false);
const shallowReadonlyGet = createGetter(true, true);

他们都调用了createGetter方法,这是一个工厂函数,通过传递isReadonly isShallow来判断是哪种类型的getter,然后创建不同的get。所以接下来我们自然而然需要分析createGetter函数。

//创造getter的工厂函数,通过是否是只读和
//是否只代理第一层创造不同的getter函数
export function createGetter(isReadonly = false, shallow = false) {
  //传递进入Proxy的get函数
  //例如const obj = {a:2}
  //    const proxy = new Proxy(obj,{
  //  get(target,key,receiver){
  //    当通过proxy.a对obj进行访问的时候,会先进入这个函数
  //     返回值将会作为proxy.a获得的值
  //   }
  // })
  return function get(target, key, receiver) {
    //1.对isReadonly isShallow等方法的处理
    //以下前面几个判断都是为了通过一些关键key判断
    //当前的对象是否是被代理的,或者是否是只读的
    //是否是只代理第一层的。
    //假设当前我们的代理是reactive类型
    //如果我们访问__v_isReactive那么返回值应该为true
    //同理访问readonly类型则返回false
    //故而这里取反
    if (key === IS_REACTIVE) {
      return !isReadonly;
    }
    //访问__v_isReadonly返回isReadonly真实值即可
    else if (key === IS_READONLY) {
      return isReadonly;
    }
    //访问__v_isShallow 返回shallow真实值即可
    else if (key === IS_SHALLOW) {
      return shallow;
    }
    //当访问__v_raw的时候,根据当前的readonly和shallow属性
    //访问不同的map表,通过map表获得代理前的对象
    else if (
      key === RAW &&
      receiver ===
        (isReadonly
          ? shallow
            ? shallowReadonlyMap
            : readonlyMap
          : shallow
          ? shallowReactiveMap
          : reactiveMap
        ).get(target)
    ) {
      return target;
    }
    //判断当前target是否是数组
    const targetIsArray = isArray(target);
    //如果调用的push pop shift unshift splice includes indexOf lastIndexOf
    //拦截这个方法
    if (!isReadonly && targetIsArray && hasOwn(arrayInstrumentations, key)) {
      return Reflect.get(arrayInstrumentations, key, receiver);
    }
    //获取访问的真实值
    const res = Reflect.get(target, key, receiver);
    //判断当前访问的key是否是内置的Symbol属性或则是否是不需要track的key
    //例如__proto__ , __v_isRef , __isVue 如果是这些属性则直接返回
    if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) {
      return res;
    }
    //如果不是只读属性 开始收集依赖 只读属性不需要收集依赖
    if (!isReadonly) {
      track(target, trackOpTypes.get, key);
    }
    //只需要代理一层,不用再进行代理了返回即可
    if (shallow) {
      return res;
    }
    //如果是访问到的value是ref类型,返回res.value
    //访问的是数组的数字属性则返回res
    if (isRef(res)) {
      return targetIsArray && isIntegerKey(key) ? res : res.value;
    }
    //如果得到的结果依然是对象继续进行深度代理
    if (isObject(res)) {
      return isReadonly ? readonly(res) : reactive(res);
    }
    return res;
  };
}

isNonTrackableKeys函数、builtInSymbols、如果数组调用了push pop includes方法该怎么处理呢?

//这里贴上源码,感兴趣的仔细阅读,不在进行讲解
const isNonTrackableKeys = makeMap(`__proto__,__v_isRef,__isVue`);
function makeMap(str, expectsLowerCase) {
  const map = Object.create(null); //创造一个空对象
  const list = str.split(","); //["__proto__","__isVUE__"]
  for (let i = 0; i < list.length; i++) {
    map[list[i]] = true;
    //{"__proto__":true,"__isVUE__":true}
  }
  //返回一个函数,用于判断是否是传递的str分割出来的某一个值
  //可以通过expectsLowerCase指定是否需要将分隔值转化为小写
  return expectsLowerCase
    ? (val) => !!map[val.toLowerCase()]
    : (val) => !!map[val];
}
//Symbol的所有属性值
export const builtInSymbols = new Set(
  //首先获取所有的Symbol的key
  Object.getOwnPropertyNames(Symbol)
     //过滤掉arguments和caller
    .filter((key) => key !== "arguments" && key !== "caller")
     //拿到所有的Symbol值
    .map((key) => Symbol[key])
    //过滤掉不是symbol的值
    .filter(shared.isSymbol)
);
//当前代理的对象是数组,且访问了pop等8个方法中的一个
if (!isReadonly && targetIsArray && hasOwn(arrayInstrumentations, key)) {
      //进行代理
      return Reflect.get(arrayInstrumentations, key, receiver);
}
const arrayInstrumentations = createArrayInstrumentations();
function createArrayInstrumentations() {
  const instrumentations = {};
  //拦截数组的方法 arr.includes()
  ["includes", "indexOf", "lastIndexOf"].forEach((key) => {
    instrumentations[key] = function (...args) {
      //这里的this指向调用当前方法的数组
      const arr = toRaw(this);
      //将当前数组中的所有元素收集依赖
      for (let i = 0, l = this.length; i < l; i++) {
        track(arr, trackOpTypes.get, i + "");
      }
      //执行函数
      const res = arr[key](...args);
      if (res === -1 || res === false) {
        return arr[key](...args.map(toRaw));
      } else {
        return res;
      }
    };
  });
  //如果使用这些方法取消收集依赖
  ["push", "pop", "shift", "unshift", "splice"].forEach((key) => {
    instrumentations[key] = function (...args) {
      //停止收集依赖 将shouldTrack变为false
      pauseTracking();
      //这里toRaw是为了防止二次执行getter,执行数组对应的方法
      const res = toRaw(this)[key].apply(this, args);
      //重新收集依赖,将shouldTrack变为true
      resetTracking();
      return res;
    };
  });
  return instrumentations;
}
//中断追踪
export function pauseTracking() {
  trackStack.push(shouldTrack);
  shouldTrack = false;
}
//重设追踪
export function resetTracking() {
  //获取之前的shouldTrack值
  const last = trackStack.pop();
  //如果trackStack中没有值shouldTrack设置为true
  shouldTrack = last === undefined ? true : last;
}

好啦,接下来我们进行track函数进行分析,看看是如何收集依赖的。

export function track(target, type, key) {
  //当调用了effect方法,会给activeEffect赋值
  if (shouldTrack && activeEffect) {
    let depsMap = targetMap.get(target);
    if (!depsMap) {
      targetMap.set(target, (depsMap = new Map()));
    }
    let dep = depsMap.get(key);
    if (!dep) {
      depsMap.set(key, (dep = createDep()));
    }
    //传递入生命周期钩子的当前effect的信息
    const eventInfo = { effect: activeEffect, target, type, key };
    trackEffects(dep, eventInfo);
  }
}
export class ReactiveEffect {
  constructor(fn, scheduler = null, scope) {
    this.fn = fn; //副作用函数
    //调度器(如果有调用器就不在执行run方法而是执行调度器)
    this.scheduler = scheduler;
    this.active = true;
    /**
     * 当前副作用被那些变量所依赖
     * 例如:
     *  effect(()=>{
     *   console.log(proxy.a)
     *  })
     *  effect(()=>{
     *   console.log(proxy.a)
     *  })
     *
     *  每一个effect的回调函数都会产生一个ReactiveEffect实例
     *  第一个effect中有proxy.a被读取,那么就会被收集依赖,则
     *  对于第一个ReactiveEffect实例来说deps中就有有proxy.a
     *  也就是target key 指向的dep,这个dep是一个集合,代表的是
     *  target key对应的dep
     */
    this.deps = [];
    this.parent = undefined;
    //TODO recordEffectScope
    recordEffectScope(this, scope);
  }
  //开始执行
  run() {
    if (!this.active) {
      return this.fn();
    }
    let parent = activeEffect;
    let lastShouldTrack = shouldTrack;
    while (parent) {
      if (parent === this) {
        return;
      }
      parent = parent.parent;
    }
    try {
      //可能有嵌套的effect,当执行到effect回调函数中有effect的时候
      //现在的activeEffect相当于最新创建的effect的父级effect
      /*
        例如:effect(()=>{
            现在指向外部的effect
            console.log(proxy.a)
            effect(()=>{
              在这里面的时候activeEffect指向内部effect
              console.log(proxy.b)
            })
            现在需要将activeEffect恢复为外部effect
            console.log(proxy.b)
        })
        当然对应的parent也应该改变,这就是try finally的作用
      */
      this.parent = activeEffect;
      //让当前的activeEffect为当前effect实例
      activeEffect = this;
      shouldTrack = true;
      //设置嵌套深度
      trackOpBit = 1 << ++effectTrackDepth;
      if (effectTrackDepth <= maxMarkerBits) {
        initDepMarkers(this);
      } else {
        cleanupEffect(this);
      }
      //执行effect副作用
      return this.fn();
    } finally {
      //退出当前effect回调函数的执行,要将全局变量退回到当前
      //effect的父级effect(回溯)
      if (effectTrackDepth <= maxMarkerBits) {
        finalizeDepMarkers(this);
      }
      //全部进行回溯
      trackOpBit = 1 << --effectTrackDepth; //恢复trackOpBit
      activeEffect = this.parent;
      shouldTrack = lastShouldTrack;
      this.parent = undefined;
      if (this.deferStop) {
        this.stop();
      }
    }
  }
  stop() {
    if (activeEffect === this) {
      this.deferStop = true;
    } else if (this.active) {
      cleanupEffect(this);
      if (this.onStop) {
        this.onStop();
      }
      this.active = false;
    }
  }
}
//收集副作用
export function trackEffects(dep, debuggerEventExtraInfo) {
  let shouldTrack = false;
  if (effectTrackDepth <= maxMarkerBits) {
    if (!newTracked(dep)) {
      dep.n |= trackOpBit;
      shouldTrack = !wasTracked(dep);
    }
  } else {
    //如果已经收集过就不收集了
    shouldTrack = !dep.has(activeEffect);
  }
  //通过上面的判断是否需要收集
  if (shouldTrack) {
    //往当前target key对应的dep中添加effect
    dep.add(activeEffect);
    //当前effect中有哪些被代理的变量的dep
    activeEffect.deps.push(dep);
    //生命周期,当真正执行track的时候调用函数
    if (activeEffect.onTrack) {
      activeEffect.onTrack({
        effect: activeEffect,
        ...debuggerEventExtraInfo,
      });
    }
  }
}

(2).handlers中的setter

//创造setter的工厂函数
export function createSetter(shallow) {
  return function set(target, key, value, receiver) {
    let oldValue = target[key]; //获取代理对象之前的value
    //旧值是ref,新值不是ref
    if (isReadonly(oldValue) && isRef(oldValue) && !isRef(value)) {
      return false;
    }
    //深度代理的情况
    if (!shallow) {
      if (!isShallow(value) && !isReadonly(value)) {
        //防止如果后面操作了value 引起二次setter
        oldValue = toRaw(oldValue);
        value = toRaw(value);
      }
      //target是对象且值为ref类型,当对这个值修改的时候应该修改ref.value
      if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
        oldValue.value = value;
        return true;
      }
    }
    //判断当前访问的key是否存在,不存在则是设置新的值
    const hadKey =
      //当前的target为数组且访问的是数字
      isArray(target) && isIntegerKey(key)
        ? Number(key) < target.length
        : hasOwn(target, key);
    //设置value
    const result = Reflect.set(target, key, value, receiver);
    if (target === toRaw(receiver)) {
      //设置新的值
      if (!hadKey) {
        trigger(target, triggerOpTypes.add, key, value);
      }
      //修改老的值
      else if (hasChanged(value, oldValue)) {
        trigger(target, triggerOpTypes.set, key, value, oldValue);
      }
    }
    return result;
  };
}
//根据不同的类型添加必要的副作用到deps中
export function trigger(target, type, key, newValue, oldValue, oldTarget) {
  const depsMap = targetMap.get(target);
  if (!depsMap) {
    return;
  }
  let deps = []; //当前要处理的所有依赖
  if (type === triggerOpTypes.clear) {
    //清空,相当于所有的元素都发生改变
    //故而全部都需要添加进依赖
    deps = [...depsMap.values()];
  }
  //拦截修改数组长度的情况
  else if (key === "length" && isArray(target)) {
    //放入key为length或者数组下标大于设置值的所以依赖
    //例如:const a = [1,2,3] a.length=1
    //那么数组长度发生了变化,2,3的依赖都应该被放入
    depsMap.forEach((dep, key) => {
      if (key === "length" || key >= newValue) {
        deps.push(dep);
      }
    });
  }
  //其他的情况获取之前在getter收集的依赖到deps中
  else {
    //将target key 指向的依赖放入deps中
    if (key !== void 0) {
      deps.push(depsMap.get(key));
    }
    //根据不同type添加不同的必要依赖到deps
    switch (type) {
      //处理添加新的值
      case triggerOpTypes.add:
        if (!isArray(target)) {
          //set或map
          deps.push(depsMap.get(ITERATE_KEY));
          if (isMap(target)) {
            deps.push(depsMap.get(MAP_KEY_ITERATE_KEY));
          }
        } else if (isIntegerKey(key)) {
          //当前修改的是数组且是新增值
          //例如 arr.length = 3 arr[4] = 8
          //此时数组长度会发生改变所以当前数组的
          //length属性依然需要被放入依赖
          deps.push(depsMap.get("length"));
        }
        break;
      case triggerOpTypes.delete:
        //处理delete...
        break;
      case triggerOpTypes.set:
        //处理map类型...
    }
  }
  //当前effect的信息
  const eventInfo = { target, type, key, newValue, oldValue, oldTarget };
  if (deps.length === 1) {
    if (deps[0]) {
      {
        triggerEffects(deps[0], eventInfo);
      }
    }
  } else {
    const effects = [];
    //扁平化所有的effect
    for (const dep of deps) {
      if (dep) {
        effects.push(...dep);
      }
    }
    //执行所有的副作用
    triggerEffects(createDep(effects), eventInfo);
  }
}
//创建dep
export const createDep = (effects) => {
  const dep = new Set(effects);
  dep.w = 0;
  dep.n = 0;
  return dep;
};
//根据trigger最终组成的deps触发所有副作用执行
function triggerEffects(dep, debuggerEventExtraInfo) {
  //拿到所有的effects 包装成数组
  const effects = isArray(dep) ? dep : [...dep];
  //含有computed属性先执行
  for (const effect of effects) {
    if (effect.computed) {
      triggerEffect(effect, debuggerEventExtraInfo);
    }
  }
  //不含有computed属性后执行
  for (const effect of effects) {
    if (!effect.computed) {
      triggerEffect(effect, debuggerEventExtraInfo);
    }
  }
}
function triggerEffect(effect, debuggerEventExtraInfo) {
  if (effect !== activeEffect || effect.allowRecurse) {
    //生命周期,对于这个effect在进行trigger的时候调用
    if (effect.onTrigger) {
      effect.onTrigger(shared.extend({ effect }, debuggerEventExtraInfo));
    }
    //如果有调度器则执行调度器否则执行run
    if (effect.scheduler) {
      effect.scheduler();
    } else {
      effect.run();
    }
  }
}

(3).handlers的deleteProperty

//处理删除属性的逻辑(统一处理)
//target:要删除属性的对象 key:要删除对象值的键
export function deleteProperty(target, key) {
  const hadKey = hasOwn(target, key); //判断删除的属性是否在
  const oldValue = target[key]; //获得旧值
  //删除属性返回值为是否删除成功
  const result = Reflect.deleteProperty(target, key);
  if (result && hadKey) {
    //触发副作用
    trigger(target, triggerOpTypes.delete, key, undefined, oldValue);
  }
  return result;
}

当调用delete obj.xxx的时候deleteProperty就会监听到,这显然是修改值的情况所以我们执行trigger,类型自然就是 "delete" ,还记得trigger中对于 "delete" 类型我们并没有讲解,下面我们看看这部分如何处理。

//将target key 指向的依赖放入deps中
    if (key !== void 0) {
      deps.push(depsMap.get(key));
}
//省略部分代码...
case triggerOpTypes.delete:
  if (!isArray(target)) {
    //添加key为iterate的依赖,后面讲这个依赖来自于哪里
    deps.push(depsMap.get(ITERATE_KEY));
    if (isMap(target)) {
      deps.push(depsMap.get(MAP_KEY_ITERATE_KEY));
    }
}
break;
//省略部分代码...
const {reactive,effect} = require('./reactivity.cjs')
const proxy = reactive({a:1})
effect(()=>{
    Object.keys(proxy)
    console.log(111)
})
effect(()=>{
    proxy.a
    console.log(111)
})
delete proxy.a
//log: 111 111 111 111 

(4).handlers的ownKeys

//拦截Object.keys getOwnPropertyNames等
export function ownKeys(target) {
  track(target, "iterate", isArray(target) ? "length" : ITERATE_KEY);
  return Reflect.ownKeys(target);
}

(5).handlers的has

//拦截foo in proxy foo in Object.create(proxy)
//with(proxy){foo} Reflect.has
export function has(target, key) {
  const result = Reflect.has(target, key); //判断是否有这个属性
  //不是Symbol或内置Symbol属性
  if (!isSymbol(key) || !builtInSymbols.has(key)) {
    track(target, "has", key);
  }
  return result;
}

好啦! 第二部分们已经完成了所有的分析,但是本文还没有完!因为篇幅过长,第三部分和第四部分我放在下一章节。我们最后再来总结一下吧!

本文总结:

本文我们写了一个简单版本的reactivity,便于大家后续理解真正的源码,然后我们分析了如何拦截array和object类型的数据,总体来说就是在effect执行的时候修改当前activeEffect的指向,然后执行effect的时候收集依赖通过proxy原生api拦截get has ownKeys的操作,完成依赖的收集,然后在set和delete的时候进行触发,并且对边际情况也进行了处理、例如数组访问修改length、使用pop push includes方法的处理等。

下文我们将会继续分析对于map set weakMap weakSet的拦截以及ref computed等api的实现,更多关于vue3源码分析reactivity的资料请关注其它相关文章!

加载全部内容

相关教程
猜你喜欢
用户评论