深入浅出Vue的响应性(上篇)

今天我们有个艰巨的挑战,那就是阅读Vue的reactive源码,一篇写完太长了,我想要长痛分成多次短痛,这样感受起来就像无痛(手动doge),只有先读的懂了,我们才能下手模仿;加油吧宝子们!!PS:本文中的代码都是简单版,多余的已被剪枝,更加简单清晰易懂; 首先我们用的例子🌰还是上上上篇那个,这段代码是我们经常用到的reactive和effect的方法,今天我们就一起来深入挖掘Vue到底做了什么事;

js 复制代码
<script>
    const { reactive, effect } = Vue;
    const obj = reactive({
      name: "张三",
    });
    console.log(obj);

    effect(() => {
      document.querySelector("#app").innerText = obj.name;
    });
    setTimeout(() => {
      obj.name = "李四";
    }, 2000);
  </script>

这是我们即将介绍的方法流程图,对照着看更容易理清思路;

首先来看reactive方法;

js 复制代码
export function reactive(target: object) {
  if (isReadonly(target)) {
    return target
  }
  return createReactiveObject(
    target,
    false,
    mutableHandlers,
    mutableCollectionHandlers,
    reactiveMap
  )
}

当前reactive方法里传入的是{name: "张三"}对象;接下来看一下createReactiveObject方法;

js 复制代码
function createReactiveObject(
  target: Target,
  isReadonly: boolean,
  baseHandlers: ProxyHandler<any>,
  collectionHandlers: ProxyHandler<any>,
  proxyMap: WeakMap<Target, any>
) {
  // 读取proxyMap的实例,此时的proxyMap是一个weakMap{}
  const existingProxy = proxyMap.get(target)
  // targetType为1
  const targetType = getTargetType(target)
  // 重点代码
  const proxy = new Proxy(
    target,
    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
  )
  // 此时target和proxy存入proxyMap,下次target存在直接用
  proxyMap.set(target, proxy)
  return proxy
}

createReactiveObject传入的target依然是{name: "张三"}对象;

第13行Proxy第一个参数传入target作为被代理对象;第二个参数传入先判断targetType,此时targetType不等于TargetType.COLLECTION,因此会用baseHandlers;,new Proxy生成一个proxy,然后被return回来;reactive函数结束之后会进入effect方法;

我们来看一下effect方法都做了什么事;

js 复制代码
export function effect<T = any>(
  fn: () => T,
  options?: ReactiveEffectOptions
): ReactiveEffectRunner {
  // ReactiveEffect是一个class,有run和stop方法
  const _effect = new ReactiveEffect(fn)
  if (!options || !options.lazy) {
    _effect.run()
  }
  const runner = _effect.run.bind(_effect) as ReactiveEffectRunner
  runner.effect = _effect
  return runner
}

effect第一个参数fn是我们传入的() => { document.querySelector("#app").innerText = obj.name; }

ReactiveEffect的run方法:

js 复制代码
run() {
    try {
      // this是当前ReactiveEffect的实例,它的parent是activeEffect
      // 此时activeEffect是undefined
      this.parent = activeEffect
      activeEffect = this
      shouldTrack = true
      // 执行我们传入的方法,document.querySelector("#app").innerText = obj.name;
      return this.fn()
    }
  }

第6行的activeEffect被赋值,此时它的fn为下图

接着会进入到baseHandlers里有get方法叫createGetter;此时get方法里的target还是{name: "张三"}对象,key是name,receiver: Proxy(Object) {name: "张三"}; Vue响应性的核心是依赖收集和依赖触发;track方法就是依赖收集的过程;

js 复制代码
function createGetter(isReadonly = false, shallow = false) {
  return function get(target: Target, key: string | symbol, receiver: object) {
    // target是对象不是数组
    const targetIsArray = isArray(target)
    // res的值是张三
    const res = Reflect.get(target, key, receiver)

    if (!isReadonly) {
      // track方法非常重要!
      track(target, TrackOpTypes.GET, key)
    }
    return res
  }
}

接着来看下核心的track方法:

js 复制代码
export function track(target: object, type: TrackOpTypes, key: unknown) {
  if (shouldTrack && activeEffect) {
    // 此时的targetMap是WeakMap{}
    let depsMap = targetMap.get(target)
    if (!depsMap) {
      // 为targetMap赋值
      targetMap.set(target, (depsMap = new Map()))
    }
    let dep = depsMap.get(key)
    if (!dep) {
      depsMap.set(key, (dep = createDep()))
    }
    trackEffects(dep, eventInfo)
  }
}

第2行的activeEffect就是上文的;

第7行为targetMap赋值之后就变成了:

这里的targeMap比较复杂,是下图一个结构:

最后执行trackEffects方法:

js 复制代码
export function trackEffects(
  dep: Dep,
  debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
  let shouldTrack = false
  if (shouldTrack) {
    dep.add(activeEffect!)
    activeEffect!.deps.push(dep)
  }
}

第7行的dep是这样的,和targetMap对比一下,会发现dep和和targetMap的value中第一个元素一样的,他们是指向同一个内存空间;

第8行里targetMap就保存了一个ReactiveEffect;这时已经完成了依赖收集的过程;依赖收集的本质上就是建立起targetMap和ReactiveEffect的关联;此时我们就可以在targetMap中用key来获取到ReactiveEffect;ReactiveEffect中存在一个fn函数,它就是我们传入的() => { document.querySelector("#app").innerText = obj.name; }方法;

track方法执行完了,再回到createGetter方法:

js 复制代码
function createGetter(isReadonly = false, shallow = false) {
  return function get(target: Target, key: string | symbol, receiver: object) {
    // target是对象不是数组
    const targetIsArray = isArray(target)
    // res的值是张三
    const res = Reflect.get(target, key, receiver)

    if (!isReadonly) {
      // track方法非常重要!
      track(target, TrackOpTypes.GET, key)
    }

    return res
  }
}

第13行return res,这时的res就是张三,这里也就是obj.name得到了张三

到这里就完成了effect的处理逻辑;

让我们总结一下effect的过程:

  1. 生成ReactiveEffect实例;
  2. 触发fn方法,从而激活getter;
  3. 建立了targetMap和activeEffect之间的联系;
相关推荐
胖虎265几秒前
实现无缝滚动无滚动条的 Element UI 表格(附完整代码)
前端·vue.js
小左OvO1 分钟前
基于百度地图JSAPI Three的城市公交客流可视化(一)——线路客流
前端
星链引擎4 分钟前
企业级智能聊天机器人 核心实现与场景落地
前端
GalaxyPokemon5 分钟前
PlayerFeedback 插件开发日志
java·服务器·前端
爱加班的猫5 分钟前
深入理解防抖与节流
前端·javascript
自由日记20 分钟前
学习中小牢骚1
前端·javascript·css
泽泽爱旅行24 分钟前
业务场景-opener.focus() 不聚焦解决
前端
VOLUN28 分钟前
Vue3 选择弹窗工厂函数:高效构建可复用数据选择组件
前端·javascript·vue.js
ErMao29 分钟前
深入理解let、const和var
前端
IT_陈寒32 分钟前
SpringBoot 3.2新特性实战:这5个隐藏功能让开发效率翻倍🚀
前端·人工智能·后端