指标

vue3响应式源码解析

Vue3的数据响应与劫持是基于古代浏览器所反对的代理对象Proxy实现的。

上面的例子简略阐明vue3的响应式数据的原理,即通过Proxy对象别离对getset劫持,在 取值和赋值两头 别离插⼊劫持的⽅法,即 track 和 trigger ——依赖的跟踪和副作⽤的触发。

const initData = {value: 1}const proxy = new Proxy(    initData, // 被代理对象    {        // handler对象        get(target, key) {            // 进行 track            return target[key]        }        set(target, key, value) {            // 进行trigger            return Reflect.set(target, key, value);        }    })// proxy 即 间接拜访批改对象// 也能够称为响应式数据(reactive/ref)

根本阐明

track: 收集依赖trigger: 触发副作用函数

代码剖析

通过上面的vue3实例代码运行,逐渐剖析源码。

// setupimport {ref, reactive, effect, computed} from "vue"export default {    ...    setup(props, context) {        const countRef = 0;        const number = reactive({ num: 1 })        effect(() => {            console.log(countRef.value)        })        const increment = () => {            countRef.value++        }                const result = computed(() => number.num ** 2)        return { countRef, number, result, increment }    }}

先用流程图简略的形容下下面代码的执行过程:

组件初始化,首先申明countRef变量与执行effect函数。effect会调用传入的参数函数fnfn函数执行到countRef.value会被getter劫持,进行track。在这个过程中会有一个全局变量targetMap,将countReffn建设关联关系。当countRef的值发生变化时,会触发以下过程:

countRef的值发生变化时,会被setter劫持,进行trigger。简略来讲就是从全局变量targetMap来获取countRef对应的fn,而后执行fn函数的过程,当然这里省略了许多细节。

初始化阶段

  • 创立响应式数据
const countRef = ref(0)const number = reactive({num: 1})
function ref(value) {  return createRef(value, false);}function createRef(rawValue, shallow) {   // 判断是否为Ref类型对象,如果是则间接返回  if (isRef(rawValue)) {    return rawValue;  }  return new RefImpl(rawValue, shallow);}

createRef函数判断传入参数rawValue是否为援用类型,如果不是,则返回RefImpl类包装的实例。

  • RefImpl
class RefImpl {  constructor(value, _shallow) {    this._shallow = _shallow;    this.dep = undefined;    this.__v_isRef = true;    this._rawValue = _shallow ? value : toRaw(value);    this._value = _shallow ? value : convert(value);  }  get value() {    trackRefValue(this); // track,进行依赖收集    return this._value;  }  set value(newVal) {    newVal = this._shallow ? newVal : toRaw(newVal);    if (hasChanged(newVal, this._rawValue)) {      this._rawValue = newVal;      this._value = this._shallow ? newVal : convert(newVal);      triggerRefValue(this, newVal); // trigger,值更改时触发更新    }  }}

RefImpl类,也是通过get/set来实现tracktrigger。这也解释了ref的包装值--通过value属性进行拜访。基于同样思路来看reactive办法

function reactive(target) {  // if trying to observe a readonly proxy, return the readonly version.  // 如果指标对象的__v_isReadonly属性为true,间接返回原对象  if (target && target["__v_isReadonly" /* IS_READONLY */ ]) {    return target;  }  return createReactiveObject(target, false, mutableHandlers, mutableCollectionHandlers, reactiveMap);}const mutableHandlers = {  get,  set,  deleteProperty,  has,  ownKeys};const mutableCollectionHandlers = {  get: /*#__PURE__*/ createInstrumentationGetter(false, false)};// 全局mapconst reactiveMap = new WeakMap();// 主逻辑function createReactiveObject(target, isReadonly, baseHandlers, collectionHandlers, proxyMap) {  if (!isObject(target)) {    {      console.warn(`value cannot be made reactive: ${String(target)}`);    }    return target;  }  // target is already a Proxy, return it.  // exception: calling readonly() on a reactive object  if (target["__v_raw" /* RAW */ ] &&    !(isReadonly && target["__v_isReactive" /* IS_REACTIVE */ ])) {    return target;  }  // 以下代码逻辑为重点:  // target already has corresponding Proxy  const existingProxy = proxyMap.get(target);  if (existingProxy) {    return existingProxy;  }  // only a whitelist of value types can be observed.  const targetType = getTargetType(target);  if (targetType === 0 /* INVALID */ ) {    return target;  }  const proxy = new Proxy(target, targetType === 2 /* COLLECTION */ ? collectionHandlers : baseHandlers);  proxyMap.set(target, proxy);  return proxy;}

createReactive办法是主逻辑,⽽且创立浅层响应的⽅法 shallowReactive ,只读⽅法 readonly 都⽤到该函数。

这⾥能看到,target: { num: 1 } 在此处被代理。如果之前曾经被代理过( proxyMap 中有缓存),则间接返 回,否则缓存起来并返回。reactive ⽅法使⽤了 Proxy 来实现代理。

数据追踪

effect执行,并调用回调办法fn,因为fn外部拜访了countRefvalue属性

effect(() =>{    console.log(countRef.value)})

这里触发了类RefImpl定义的get办法

class RefImpl {    get value() {        trackRefValue(this);        return this._value;    }    }// 这⾥有条件地使⽤ trackEffects 保护着 ref 实例属性 dep 与// 沉闷的effet的映射,说人话就是:包装的数据在第一次被effect内// fn函数拜访时,包装对象顺便把这个fn也存了下来function trackRefValue(ref) {  if (isTracking()) {    ref = toRaw(ref);    // 如果是第一次拜访,dep属性为undefined,则创立dep属性收集依赖    if (!ref.dep) {      ref.dep = createDep();    } {      // 重点:触发副作用函数        trackEffects(ref.dep, {        target: ref,        type: "get" /* GET */ ,        key: 'value'      });    }  }}function trackEffects(dep, debuggerEventExtraInfo) {  let shouldTrack = false;  if (effectTrackDepth <= maxMarkerBits) {    if (!newTracked(dep)) {      dep.n |= trackOpBit; // set newly tracked      shouldTrack = !wasTracked(dep);    }  } else {    // Full cleanup mode.    shouldTrack = !dep.has(activeEffect);  }   //  activeEffect 是全局变量,在执⾏ effect 时会指向⼀个蕴含了 fn 的实例。   //  换句话说,此处 dep.add(activeEffect)   //   等效于 ref.dep.add(wrapper(fn)),wrapper 是过程的简化  if (shouldTrack) {    dep.add(activeEffect); // 做个标记 coordinate1    activeEffect.deps.push(dep);    if (activeEffect.onTrack) {      activeEffect.onTrack(        Object.assign(          {            effect: activeEffect,          },          debuggerEventExtraInfo        )      );    }  }}

状态更新阶段

以 ref 创立的数据源为例, countRef.value++ 从下⾯开始

class RefImpl {  set value(newVal) {    newVal = this._shallow ? newVal : toRaw(newVal);    if (hasChanged(newVal, this._rawValue)) {      this._rawValue = newVal;      this._value = this._shallow ? newVal : convert(newVal);      triggerRefValue(this, newVal); // 值得更新触发trigger    }  }}function triggerRefValue(ref, newVal) {  ref = toRaw(ref);  // 如果该ref没有dep属性,阐明之前没有触发依赖收集。  if (ref.dep) { // 回到上⾯标记的地⽅ coordinate1    {      triggerEffects(ref.dep, {        target: ref,        type: "set" /* SET */,        key: "value",        newValue: newVal,      });    }  }}

标记的地位证实包装值 ref(0) 通过 dep 对将来要执⾏的 fn 是存在引⽤关系的,⽽ triggerEffect ⽅法就 依据这个存在的关系,⼀旦 set 时就触发它!

function triggerEffects(dep, debuggerEventExtraInfo) {  // spread into array for stabilization  for (const effect of isArray(dep) ? dep : [...dep]) {    if (effect !== activeEffect || effect.allowRecurse) {      if (effect.onTrigger) {        effect.onTrigger(extend({ effect }, debuggerEventExtraInfo));      }      if (effect.scheduler) {        effect.scheduler(); // 这是 fn 在微工作队列中执⾏的地⽅      } else {        effect.run(); // 这是 fn 同步执⾏的地⽅      }    }  }}

在数据跟踪阶段,通过activeEffect把ref的dep属性将执行的fn关联起来,再到状态更新阶段,从新遍历执行ref的def关联的fn. 缕清主线后,再略微关注⼀下 effect 的逻辑,就能把 scheduler, run 与 fn 联 系起来了:

function effect(fn, options) {  if (fn.effect) {    fn = fn.effect.fn;  }  const _effect = new ReactiveEffect(fn);  if (options) {    extend(_effect, options);    if (options.scope) recordEffectScope(_effect, options.scope);  }  // effect(fn)首次执行  if (!options || !options.lazy) {    _effect.run();  }  const runner = _effect.run.bind(_effect);  runner.effect = _effect;  return runner;}

这里传入的fn函数参数,被ReactiveEffect类包装成实例

const effectStack = [];class ReactiveEffect {  constructor(fn, scheduler = null, scope) {    this.fn = fn;    this.scheduler = scheduler;    this.active = true;    this.deps = [];    recordEffectScope(this, scope);  }  run() {    if (!this.active) {      return this.fn();    }    if (!effectStack.includes(this)) { // 实例不在栈中时执行,防止反复触发执行fn      try {        effectStack.push((activeEffect = this));        enableTracking();        trackOpBit = 1 << ++effectTrackDepth;        if (effectTrackDepth <= maxMarkerBits) {          initDepMarkers(this);        } else {          cleanupEffect(this);        }        return this.fn();      } finally {        if (effectTrackDepth <= maxMarkerBits) {          finalizeDepMarkers(this);        }        trackOpBit = 1 << --effectTrackDepth;        resetTracking();        effectStack.pop();        const n = effectStack.length;        activeEffect = n > 0 ? effectStack[n - 1] : undefined;      }    }  }  stop() {    if (this.active) {      cleanupEffect(this);      if (this.onStop) {        this.onStop();      }      this.active = false;    }  }}

上⾯的 ref ⽅法创立数据与更新的⼀整套流程,其实 reactive 创立的数据,也有相似 的逻辑,区别就在于 Proxyhandler 局部:

const proxy = new Proxy(    target,    targetType === 2 /* COLLECTION */ ? collectionHandlers : baseHandlers  );

baseHandlers 为例(这⾥是形参),找到实参 mutableHandlers

const mutableHandlers = {  get,  set,  deleteProperty,  has,  ownKeys,};// 咱们能够判定,这⾥的 get/set 就是进⾏ track 和 trigger 的地⽅。找到它const get = /*#__PURE__*/ createGetter();function createGetter(isReadonly = false, shallow = false) {  return function get(target, key, receiver) {    if (key === "__v_isReactive" /* IS_REACTIVE */) {      return !isReadonly;    } else if (key === "__v_isReadonly" /* IS_READONLY */) {      return isReadonly;    } else if (      key === "__v_raw" /* RAW */ &&      receiver ===        (isReadonly          ? shallow            ? shallowReadonlyMap            : readonlyMap          : shallow          ? shallowReactiveMap          : reactiveMap        ).get(target)    ) {      return target;    }    const targetIsArray = isArray(target);    if (!isReadonly && targetIsArray && hasOwn(arrayInstrumentations, key)) {       // arrayInstrumentations 内也有 track,不再展现,关注主线      return Reflect.get(arrayInstrumentations, key, receiver);    }    const res = Reflect.get(target, key, receiver);    if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) {      return res;    }    if (!isReadonly) {      track(target, "get" /* GET */, key); // 呈现了与 ref 拦挡⼀样的逻辑    }    ...    return res;  };}function track(target, type, key) {  if (!isTracking()) {    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 = createDep()));  }  const eventInfo = { effect: activeEffect, target, type, key };  trackEffects(dep, eventInfo); // 与 trackRefValue 必由之路,略}
  • set
const set = /*#__PURE__*/ createSetter()function createSetter(shallow = false) {  return function set(target, key, value, receiver) {    let oldValue = target[key];    if (!shallow) {      value = toRaw(value);      oldValue = toRaw(oldValue);      if (!isArray(target) && isRef(oldValue) && !isRef(value)) {        oldValue.value = value;        return true;      }    }    const hadKey =      isArray(target) && isIntegerKey(key)        ? Number(key) < target.length        : hasOwn(target, key);    const result = Reflect.set(target, key, value, receiver);    // don't trigger if target is something up in the prototype chain of original    if (target === toRaw(receiver)) {      if (!hadKey) {        trigger(target, "add" /* ADD */, key, value); // 与ref一样触发trigger      } else if (hasChanged(value, oldValue)) {        trigger(target, "set" /* SET */, key, value, oldValue);      }    }    return result;  };}function trigger(target, type, key, newValue, oldValue, oldTarget) {  const depsMap = targetMap.get(target);  if (!depsMap) {    // never been tracked    return;  }  let deps = [];  if (type === "clear" /* CLEAR */) {    // collection being cleared    // trigger all effects for target    deps = [...depsMap.values()];  } else if (key === "length" && isArray(target)) {    depsMap.forEach((dep, key) => {      if (key === "length" || key >= newValue) {        deps.push(dep);      }    });  } else {    // schedule runs for SET | ADD | DELETE    if (key !== void 0) {      deps.push(depsMap.get(key));    }    // also run for iteration key on ADD | DELETE | Map.SET    switch (type) {      case "add" /* ADD */:        if (!isArray(target)) {          deps.push(depsMap.get(ITERATE_KEY));          if (isMap(target)) {            deps.push(depsMap.get(MAP_KEY_ITERATE_KEY));          }        } else if (isIntegerKey(key)) {          // new index added to array -> length changes          deps.push(depsMap.get("length"));        }        break;      case "delete" /* DELETE */:        if (!isArray(target)) {          deps.push(depsMap.get(ITERATE_KEY));          if (isMap(target)) {            deps.push(depsMap.get(MAP_KEY_ITERATE_KEY));          }        }        break;      case "set" /* SET */:        if (isMap(target)) {          deps.push(depsMap.get(ITERATE_KEY));        }        break;    }  }        function trigger(target, type, key, newValue, oldValue, oldTarget) {  ...  const eventInfo = { target, type, key, newValue, oldValue, oldTarget };  if (deps.length === 1) {    if (deps[0]) {      {        triggerEffects(deps[0], eventInfo); // 与 triggerRefValue 必由之路,略      }    }  } else {    const effects = [];    for (const dep of deps) {      if (dep) {        effects.push(...dep);      }    }    {      triggerEffects(createDep(effects), eventInfo);    }  }}

其实 watch ⽅法,也是基于 effect 做的封装,不再赘述。

参考文章

Proxy