关于vue3:Vue3响应式原理

4次阅读

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

指标

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 实例代码运行,逐渐剖析源码。

// setup
import {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)
};

// 全局 map
const 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

正文完
 0