深入浅出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之间的联系;
相关推荐
并不会1 小时前
常见 CSS 选择器用法
前端·css·学习·html·前端开发·css选择器
衣乌安、1 小时前
【CSS】居中样式
前端·css·css3
兔老大的胡萝卜1 小时前
ppk谈JavaScript,悟透JavaScript,精通CSS高级Web,JavaScript DOM编程艺术,高性能JavaScript pdf
前端·javascript
低代码布道师1 小时前
CSS的三个重点
前端·css
耶啵奶膘2 小时前
uniapp-是否删除
linux·前端·uni-app
王哈哈^_^4 小时前
【数据集】【YOLO】【目标检测】交通事故识别数据集 8939 张,YOLO道路事故目标检测实战训练教程!
前端·人工智能·深度学习·yolo·目标检测·计算机视觉·pyqt
cs_dn_Jie4 小时前
钉钉 H5 微应用 手机端调试
前端·javascript·vue.js·vue·钉钉
开心工作室_kaic5 小时前
ssm068海鲜自助餐厅系统+vue(论文+源码)_kaic
前端·javascript·vue.js
有梦想的刺儿5 小时前
webWorker基本用法
前端·javascript·vue.js
cy玩具6 小时前
点击评论详情,跳到评论页面,携带对象参数写法:
前端