关于vue.js:vue3响应式数据最全最细致解析vue3源码解析持续更新中

3次阅读

共计 3535 个字符,预计需要花费 9 分钟才能阅读完成。

本文是我新开的坑的第一篇文章,这个坑就是 vue3,接下来我会围绕着 vue3 进行一系列的动作,包含但不限于:

  • 残缺的源码解析
  • jsx 工程最佳实际
  • 个性化的 jsx 计划
  • vue3 生态库开发(目前有一个正在进行)
  • 以及可能的自定义一个 vue3 的 runtime

对于源码解析,网站曾经上线,vue3 源码解析,最佳实际,网站是逐行代码模式的解析,更多关注于源码,而在掘金上分享的文章则相似于总结,会用更复合一篇文章的构造来写。如果你想继续跟进 vue3 源码,能够关上后面的网站关注我。

那么,开始!

vue3 最大的变动莫过于其对于响应式原理的重构,以及其新公布的composition api,本文聚焦于前者,来深度分析一下 vue3 中响应式到底是怎么实现的。

咱们以reactiveAPI 为例,

const Comp = {setup() {
        const state = reactive({a: 'jokcy'})
        
        return () => {return <input value={state.a} onChange={(e) => state.a = e.targent.value} />
        }
    }
}

咱们看下面的例子,这个例子很简略,创立了一个组件,他有一个响应式的数据对象,而后 render 外面的 input 的 value 绑定的是 state.a 以及他的 onChange 会批改 state.a。这是非常简单且直观的一个数据绑定的例子,而这个逻辑能实现的根本原因,是咱们在调用state.a = xxx 的时候,vue 会从新渲染咱们 return 的 render 函数,来更新节点

而篇文章就是要来看一下,咱们通过 reactive 创立的对象,到底有什么魔力,可能帮咱们实现这个工作。

其实自身 API 是很简略的,传入一个对象,返回一个 reactive 对象,创立的办法是createReactiveObject

export function reactive(target: object) {
  // if trying to observe a readonly proxy, return the readonly version.
  if (target && (target as Target)[ReactiveFlags.IS_READONLY]) {return target;}
  return createReactiveObject(
    target,
    false,
    mutableHandlers,
    mutableCollectionHandlers
  );
}
function createReactiveObject(
  target: Target,
  isReadonly: boolean,
  baseHandlers: ProxyHandler<any>,
  collectionHandlers: ProxyHandler<any>
) {
  // 后面都是一些对象是否曾经 proxy 等的判断逻辑,这里就不展现了

  const observed = new Proxy(
    target,
    collectionTypes.has(target.constructor) ? collectionHandlers : baseHandlers
  );
  def(
    target,
    isReadonly ? ReactiveFlags.READONLY : ReactiveFlags.REACTIVE,
    observed
  );
  return observed;
}

那么这里最重要的就是 new Proxy 了,能够说了解 vue3 的响应式原理过程就是了解这个 proxy 创立的过程,而理解这个过程,次要就是看第二个参数,在这里就是 collectionHandlers 或者baseHandlers,大部分是后者,前者次要针对,Set、Map 等。

那么咱们就来看baseHandlers

export const mutableHandlers: ProxyHandler<object> = {
  get,
  set,
  deleteProperty,
  has,
  ownKeys,
};

可见 vue 代理了这几个操作,那么咱们一个个看这几个操作做了啥:

function get(target: object, key: string | symbol, receiver: object) {
  // ... 外部 key 的货足

  // 对于数组的一些非凡解决
  const targetIsArray = isArray(target);
  if (targetIsArray && hasOwn(arrayInstrumentations, key)) {return Reflect.get(arrayInstrumentations, key, receiver);
  }

  // 获取申请值
  const res = Reflect.get(target, key, receiver);

  // ... 如果是外部值的获取则间接返回 res

  if (!isReadonly) {track(target, TrackOpTypes.GET, key);
  }

  // 返回的一些解决

  return res;
}

这里的重点就在于track(target, TrackOpTypes.GET, key);,这个是咱们失常获取值的时候都会执行到的代码:

export function track(target: object, type: TrackOpTypes, key: unknown) {if (!shouldTrack || 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);
    if (__DEV__ && activeEffect.options.onTrack) {
      activeEffect.options.onTrack({
        effect: activeEffect,
        target,
        type,
        key,
      });
    }
  }
}

好了,重点来了,咱们逐行剖析。

第一个 if,就是依据环境变量的shouldTrack 来判断是否要进行跟踪,如果你曾经看过我的源码解析中的 processComponent 的章节,那么你当初应该就是恍然大悟的感觉。因为在执行 processComponent 外面的 setup 的时候,咱们顺便敞开了 track,而那时候就是把shouldTrack 改为了false

let depsMap = targetMap.get(target)这行,targetMap是一个 map,用来记录所有的响应对象。之后如果目前没有记录该对象,那么就从新记录。

这个 target 对应的也是一个 map,他会对每个 key 建设一个 set。

最初要记录这次的 effect 了,这里所谓的 effect 是什么呢?就是以后正在调用这个对象的函数。在咱们的例子外面,就是 return 回去的 render 函数,这个函数在执行的时候会调用 state.a 所以会进入 proxy 对于 get 的代理,这个时候 proxy 就调用了 track,那么这时候的activeEffect 就是这个 render 办法。这里的 effect 就是,当 state.a 改变的时候,咱们须要从新执行该 render 办法来进行渲染。

那么他是什么时候被设置的呢?在 mount 章节的时候咱们提到了,在执行 render 办法的时候,咱们执行这样一句代码 instance.update = effect(function componentEffect()...),就是在这里调用的effect 办法外面,把 activeEffect 记录为 componentEffect,而这个componentEffect 外面则运行了 render 办法。

另外这个 getHandler 外面有句代码也挺有意思的:

if (isObject(res)) {return isReadonly ? readonly(res) : reactive(res);
}

在获取属性的时候,如果返回的值是对象的时候,才会对其执行 reactive,这也就是提早解决,而且readonly 的话是不执行 reactive 的。

OK,到这里咱们曾经晓得了在执行 render 函数的时候,因为咱们调用了 state.a 所以这个函数就相当于依赖state.a,这在 vue3 外面被称为effect

那么下一篇文章,咱们就来讲一讲,这些 effectstate.a变动的时候是如何被调用的,敬请期待。

正文完
 0