vue effectScope 解析

33 阅读4分钟
1.外部
<script setup lang="ts">import { effectScope, ref, watchEffect, computed, onScopeDispose } from 'vue';const count = ref(0);let outerScope: ReturnType<typeof effectScope> | null = null;let innerScopeA: ReturnType<typeof effectScope> | null = null; // 常规子 scope(随父级停止)let innerScopeB: ReturnType<typeof effectScope> | null = null; // detached 子 scope(需手动停止)function start() {  // 重新开始前确保旧的 outer 清理  outerScope?.stop();  outerScope = effectScope(); // 创建  outerScope.run(() => {    console.log('[outer] started');    watchEffect(() => {      console.log('[outer] count =', count.value);    });    //scope A:默认(非 detached),会随 outer 停止而停止    innerScopeA = effectScope();    innerScopeA.run(() => {      const doubled = computed(() => count.value * 2);      watchEffect(() => {        console.log('[innerA] doubled =', doubled.value);      });      onScopeDispose(() => console.log('[innerA] cleanup'));    });    //scope B:detached,不会随 outer 停止,需手动 stop    innerScopeB = effectScope(true);    innerScopeB.run(() => {      const tripled = computed(() => count.value * 3);      watchEffect(() => {        console.log('[innerB-detached] tripled =', tripled.value);      });      onScopeDispose(() => console.log('[innerB-detached] cleanup'));    });    onScopeDispose(() => console.log('[outer] cleanup'));  });}function inc() {  count.value++;}function stopInnerA() {  innerScopeA?.stop();  innerScopeA = null;}function stopOuter() {  outerScope?.stop();  outerScope = null;}function stopInnerB() {  innerScopeB?.stop();  innerScopeB = null;}// 组件初始化时立即开启一次start();</script><template>  <div style="display:flex; gap:8px; flex-wrap:wrap;">    <button @click="inc">+1 (count: {{ count }})</button>    <button @click="stopInnerA">stop innerA</button>    <button @click="stopOuter">stop outer (会级联停 innerA)</button>    <button @click="stopInnerB">stop innerB (detached)</button>    <button @click="start">restart all</button>  </div></template>

2.内部

3.分析

ui 数据模型 可分为 定义 修改 使用

vue 1.定义 提供相应式API获取代理对象 

       2.修改 通过set触发修改 

       3.使用 代理对象get会有触发收集副作用 

提供effectScope管理副作用

1.初始化副作用域->2.执行副作用域(

2.1.转移控制权 定位当前副作用作用域->

2.2.执行副作用域函数(

       2.2.1响应式副作用触发记录副作用域

       2.2.2响应式对象的获取触发副作用域收集副作用

       2.2.3记录副作用域的关闭函数

       2.2.4  子effectScope 重复外面流程

)->

2.3转移控制权 定位父副作用作用域

)->3.关闭副作用域

整个流程
const count = ref(0);

function ref(value) {  return createRef(value, false);}

function createRef(rawValue, shallow) {  if (isRef(rawValue)) {    return rawValue;  }  return new RefImpl(rawValue, shallow);}
class RefImpl {  constructor(value, __v_isShallow) {    this.__v_isShallow = __v_isShallow;    this.dep = void 0;    this.__v_isRef = true;    this._rawValue = __v_isShallow ? value : toRaw(value);    this._value = __v_isShallow ? value : toReactive(value);  }start();outerScope?.stop();

终止上一次
 outerScope = effectScope();
function effectScope(detached) {  return new EffectScope(detached);}

class EffectScope {  constructor(detached = false) {
        this.detached = detached; 
    是否父副作用域    /**     * @internal     */    this._active = true;    /**     * @internal     */    this.effects = [];    /**     * @internal     */    this.cleanups = [];    this.parent = activeEffectScope;    if (!detached && activeEffectScope) {      this.index = (activeEffectScope.scopes || (activeEffectScope.scopes = [])).push(        this      ) - 1;    }
     记录父子副作用域关系 
       父作用域随着 组件卸载而停止 然后递归触发 子作用域清楚
       子作用域可以主动清除  }

outerScope.run(() => {run(fn) {    if (this._active) {      const currentEffectScope = activeEffectScope;
      转交控制权      try {        activeEffectScope = this;        return fn();      } finally {        activeEffectScope = currentEffectScope;
      转交控制权
      }    } else if (!!(process.env.NODE_ENV !== "production")) {      warn$2(`cannot run an inactive effect scope.`);    }  }
 watchEffect(() => {      console.log('[outer] count =', count.value);    });

function watchEffect(effect, options) {  return doWatch(effect, null, options);}
//---------------------------doWatch代码完整版--------------------------------------------function doWatch(source, cb, {  immediate,  deep,  flush,  once,  onTrack,  onTrigger} = EMPTY_OBJ) {  if (cb && once) {    const _cb = cb;    cb = (...args) => {      _cb(...args);      unwatch();    };  }  if (!!(process.env.NODE_ENV !== "production") && deep !== void 0 && typeof deep === "number") {    warn$1(      `watch() "deep" option with number value will be used as watch depth in future versions. Please use a boolean instead to avoid potential breakage.`    );  }  if (!!(process.env.NODE_ENV !== "production") && !cb) {    if (immediate !== void 0) {      warn$1(        `watch() "immediate" option is only respected when using the watch(source, callback, options?) signature.`      );    }    if (deep !== void 0) {      warn$1(        `watch() "deep" option is only respected when using the watch(source, callback, options?) signature.`      );    }    if (once !== void 0) {      warn$1(        `watch() "once" option is only respected when using the watch(source, callback, options?) signature.`      );    }  } const warnInvalidSource = (s) => {    warn$1(      `Invalid watch source: `,      s,      `A watch source can only be a getter/effect function, a ref, a reactive object, or an array of these types.`    );  };  const instance = currentInstance;


  const reactiveGetter = (source2) => deep === true ? source2 : (    // for deep: false, only traverse root-level properties    traverse(source2, deep === false ? 1 : void 0)  );  let getter;  let forceTrigger = false;  let isMultiSource = false;  if (isRef(source)) {    getter = () => source.value;    forceTrigger = isShallow(source);  } else if (isReactive(source)) {    getter = () => reactiveGetter(source);    forceTrigger = true;  } else if (isArray(source)) {    isMultiSource = true;    forceTrigger = source.some((s) => isReactive(s) || isShallow(s));    getter = () => source.map((s) => {      if (isRef(s)) {        return s.value;      } else if (isReactive(s)) {        return reactiveGetter(s);      } else if (isFunction(s)) {        return callWithErrorHandling(s, instance, 2);      } else {        !!(process.env.NODE_ENV !== "production") && warnInvalidSource(s);      }    });  } else if (isFunction(source)) {    if (cb) {      getter = () => callWithErrorHandling(source, instance, 2);    } else {      getter = () => {        if (cleanup) {          cleanup();        }        return callWithAsyncErrorHandling(          source,          instance,          3,          [onCleanup]        );      };    }  } else {    getter = NOOP;    !!(process.env.NODE_ENV !== "production") && warnInvalidSource(source);  }  if (cb && deep) {    const baseGetter = getter;    getter = () => traverse(baseGetter());  }  let cleanup;  let onCleanup = (fn) => {    cleanup = effect.onStop = () => {      callWithErrorHandling(fn, instance, 4);      cleanup = effect.onStop = void 0;    };  };  let ssrCleanup;  if (isInSSRComponentSetup) {    onCleanup = NOOP;    if (!cb) {      getter();    } else if (immediate) {      callWithAsyncErrorHandling(cb, instance, 3, [        getter(),        isMultiSource ? [] : void 0,        onCleanup      ]);    }    if (flush === "sync") {      const ctx = useSSRContext();      ssrCleanup = ctx.__watcherHandles || (ctx.__watcherHandles = []);    } else {      return NOOP;    }  }  let oldValue = isMultiSource ? new Array(source.length).fill(INITIAL_WATCHER_VALUE) : INITIAL_WATCHER_VALUE;  const job = () => {    if (!effect.active || !effect.dirty) {      return;    }    if (cb) {      const newValue = effect.run();      if (deep || forceTrigger || (isMultiSource ? newValue.some((v, i) => hasChanged(v, oldValue[i])) : hasChanged(newValue, oldValue)) || false) {        if (cleanup) {          cleanup();        }        callWithAsyncErrorHandling(cb, instance, 3, [          newValue,          // pass undefined as the old value when it's changed for the first time          oldValue === INITIAL_WATCHER_VALUE ? void 0 : isMultiSource && oldValue[0] === INITIAL_WATCHER_VALUE ? [] : oldValue,          onCleanup        ]);        oldValue = newValue;      }    } else {      effect.run();    }  };  job.allowRecurse = !!cb;  let scheduler;  if (flush === "sync") {    scheduler = job;  } else if (flush === "post") {    scheduler = () => queuePostRenderEffect(job, instance && instance.suspense);  } else {    job.pre = true;    if (instance)      job.id = instance.uid;    scheduler = () => queueJob(job);  }  const effect = new ReactiveEffect(getter, NOOP, scheduler);

  const scope = getCurrentScope();  const unwatch = () => {    effect.stop();    if (scope) {      remove(scope.effects, effect);    }  };  if (!!(process.env.NODE_ENV !== "production")) {    effect.onTrack = onTrack;    effect.onTrigger = onTrigger;  }  if (cb) {    if (immediate) {      job();    } else {      oldValue = effect.run();    }  } else if (flush === "post") {    queuePostRenderEffect(      effect.run.bind(effect),      instance && instance.suspense    );  } else {    effect.run();  }  if (ssrCleanup)    ssrCleanup.push(unwatch);  return unwatch;}//---------------------------doWatch代码简版--------------------------------------------

function doWatch(source, cb, {  immediate,  deep,  flush,  once,  onTrack,  onTrigger} = EMPTY_OBJ) {

  const effect = new ReactiveEffect(getter, NOOP, scheduler);

class ReactiveEffect {  constructor(fn, trigger, scheduler, scope) {    this.fn = fn;    this.trigger = trigger;    this.scheduler = scheduler;    this.active = true;    this.deps = [];    /**     * @internal     */    this._dirtyLevel = 4;    /**     * @internal     */    this._trackId = 0;    /**     * @internal     */    this._runnings = 0;    /**     * @internal     */    this._shouldSchedule = false;    /**     * @internal     */    this._depsLength = 0;    recordEffectScope(this, scope);  }

function recordEffectScope(effect, scope = activeEffectScope) {  if (scope && scope.active) {    scope.effects.push(effect);  }}  const scope = getCurrentScope();  const unwatch = () => {    effect.stop();    if (scope) {      remove(scope.effects, effect);    }  };

  if (ssrCleanup)    ssrCleanup.push(unwatch);  return unwatch;

  使用 const unwatch = watch(()=>{})   unwatch()

class ReactiveEffect {  constructor(fn, trigger, scheduler, scope) {    this.fn = fn;    this.trigger = trigger;    this.scheduler = scheduler;    this.active = true;    this.deps = [];    /**     * @internal     */    this._dirtyLevel = 4;    /**     * @internal     */    this._trackId = 0;    /**     * @internal     */    this._runnings = 0;    /**     * @internal     */    this._shouldSchedule = false;    /**     * @internal     */    this._depsLength = 0;    recordEffectScope(this, scope);  }
function recordEffectScope(effect, scope = activeEffectScope) {  if (scope && scope.active) {    scope.effects.push(effect);  }}    effect.run();run() {    this._dirtyLevel = 0;    if (!this.active) {      return this.fn();    }    let lastShouldTrack = shouldTrack;    let lastEffect = activeEffect;    try {      shouldTrack = true;      activeEffect = this;      this._runnings++;      preCleanupEffect(this);      return this.fn();    } finally {      postCleanupEffect(this);      this._runnings--;      activeEffect = lastEffect;      shouldTrack = lastShouldTrack;    }  }

function preCleanupEffect(effect2) {  effect2._trackId++;  effect2._depsLength = 0;}function postCleanupEffect(effect2) {  if (effect2.deps.length > effect2._depsLength) {    for (let i = effect2._depsLength; i < effect2.deps.length; i++) {      cleanupDepEffect(effect2.deps[i], effect2);    }    effect2.deps.length = effect2._depsLength;  }}
function cleanupDepEffect(dep, effect2) {  const trackId = dep.get(effect2);  if (trackId !== void 0 && effect2._trackId !== trackId) {    dep.delete(effect2);    if (dep.size === 0) {      dep.cleanup();    }  }}


//--------------------子副作用域重复外面流程------------------
.................//--------------------子副作用域重复外面流程------------------

onScopeDispose(() => console.log('[outer] cleanup'));function onScopeDispose(fn) {  if (activeEffectScope) {    activeEffectScope.cleanups.push(fn);  } else if (!!(process.env.NODE_ENV !== "production")) {    warn$2(      `onScopeDispose() is called when there is no active effect scope to be associated with.`    );  }}

  outerScope?.stop();stop(fromParent) {    if (this._active) {      let i, l;      for (i = 0, l = this.effects.length; i < l; i++) {        this.effects[i].stop();      }      for (i = 0, l = this.cleanups.length; i < l; i++) {        this.cleanups[i]();      }      if (this.scopes) {        for (i = 0, l = this.scopes.length; i < l; i++) {          this.scopes[i].stop(true);        }      }      if (!this.detached && this.parent && !fromParent) {        const last = this.parent.scopes.pop();        if (last && last !== this) {          this.parent.scopes[this.index] = last;          last.index = this.index;        }      }      this.parent = void 0;      this._active = false;    }  }


 stop() {    var _a;    if (this.active) {      preCleanupEffect(this);      postCleanupEffect(this);      (_a = this.onStop) == null ? void 0 : _a.call(this);      this.active = false;    }  }





简化执行流程代码

const count = ref(0);
function ref(value) {  return createRef(value, false);}
function createRef(rawValue, shallow) {  if (isRef(rawValue)) {    return rawValue;  }  return new RefImpl(rawValue, shallow);}
class RefImpl {  constructor(value, __v_isShallow) {    this.__v_isShallow = __v_isShallow;    this.dep = void 0;    this.__v_isRef = true;    this._rawValue = __v_isShallow ? value : toRaw(value);    this._value = __v_isShallow ? value : toReactive(value);  }


start();
function start() {  // 重新开始前确保旧的 outer 清理  outerScope?.stop();

  outerScope = effectScope(); // 创建

function effectScope(detached) {  return new EffectScope(detached);}let activeEffectScope;class EffectScope {  constructor(detached = false) {    this.detached = detached;    /**     * @internal     */    this._active = true;    /**     * @internal     */    this.effects = [];    /**     * @internal     */    this.cleanups = [];    this.parent = activeEffectScope;    if (!detached && activeEffectScope) {      this.index = (activeEffectScope.scopes || (activeEffectScope.scopes = [])).push(        this      ) - 1;    }  }
  outerScope.run(() => {

 run(fn) {    if (this._active) {      const currentEffectScope = activeEffectScope;

 try {        activeEffectScope = this;        return fn();

  watchEffect(() => {      console.log('[outer] count =', count.value);    });
function watchEffect(effect, options) {  return doWatch(effect, null, options);}
function doWatch(source, cb, {  immediate,  deep,  flush,  once,  onTrack,  onTrigger} = EMPTY_OBJ) {
  
##-------------代码太长省略代码--------
  const effect = new ReactiveEffect(getter, NOOP, scheduler);

class ReactiveEffect {  constructor(fn, trigger, scheduler, scope) {    this.fn = fn;    this.trigger = trigger;    this.scheduler = scheduler;    this.active = true;    this.deps = [];    /**     * @internal     */    this._dirtyLevel = 4;    /**     * @internal     */    this._trackId = 0;    /**     * @internal     */    this._runnings = 0;    /**     * @internal     */    this._shouldSchedule = false;    /**     * @internal     */    this._depsLength = 0;    recordEffectScope(this, scope);  }

function recordEffectScope(effect, scope = activeEffectScope) {  if (scope && scope.active) {    scope.effects.push(effect);  }}  const scope = getCurrentScope();function getCurrentScope() {  return activeEffectScope;}

## -------------------重复子副作用域---------------
const doubled = computed(() => count.value * 2);const computed = (getterOrOptions, debugOptions) => {  const c = computed$1(getterOrOptions, debugOptions, isInSSRComponentSetup);  if (!!(process.env.NODE_ENV !== "production")) {    const i = getCurrentInstance();    if (i && i.appContext.config.warnRecursiveComputed) {      c._warnRecursive = true;    }  }  return c;};function computed$1(getterOrOptions, debugOptions, isSSR = false) {  let getter;  let setter;  const onlyGetter = isFunction(getterOrOptions);  if (onlyGetter) {    getter = getterOrOptions;    setter = !!(process.env.NODE_ENV !== "production") ? () => {      warn$2("Write operation failed: computed value is readonly");    } : NOOP;  } else {    getter = getterOrOptions.get;    setter = getterOrOptions.set;  }  const cRef = new ComputedRefImpl(getter, setter, onlyGetter || !setter, isSSR);  if (!!(process.env.NODE_ENV !== "production") && debugOptions && !isSSR) {    cRef.effect.onTrack = debugOptions.onTrack;    cRef.effect.onTrigger = debugOptions.onTrigger;  }  return cRef;}

class ComputedRefImpl {  constructor(getter, _setter, isReadonly, isSSR) {    this.getter = getter;    this._setter = _setter;    this.dep = void 0;    this.__v_isRef = true;    this["__v_isReadonly"] = false;    this.effect = new ReactiveEffect(      () => getter(this._value),      () => triggerRefValue(        this,        this.effect._dirtyLevel === 2 ? 2 : 3      )    );    this.effect.computed = this;    this.effect.active = this._cacheable = !isSSR;    this["__v_isReadonly"] = isReadonly;  }
  this.effect = new ReactiveEffect(      () => getter(this._value),      () => triggerRefValue(        this,        this.effect._dirtyLevel === 2 ? 2 : 3      )    );function triggerRefValue(ref2, dirtyLevel = 4, newVal) {  ref2 = toRaw(ref2);  const dep = ref2.dep;  if (dep) {    triggerEffects(      dep,      dirtyLevel,      !!(process.env.NODE_ENV !== "production") ? {        target: ref2,        type: "set",        key: "value",        newValue: newVal      } : void 0    );  }}function triggerEffects(dep, dirtyLevel, debuggerEventExtraInfo) {  var _a;  pauseScheduling();  for (const effect2 of dep.keys()) {    let tracking;    if (effect2._dirtyLevel < dirtyLevel && (tracking != null ? tracking : tracking = dep.get(effect2) === effect2._trackId)) {      effect2._shouldSchedule || (effect2._shouldSchedule = effect2._dirtyLevel === 0);      effect2._dirtyLevel = dirtyLevel;    }    if (effect2._shouldSchedule && (tracking != null ? tracking : tracking = dep.get(effect2) === effect2._trackId)) {      if (!!(process.env.NODE_ENV !== "production")) {        (_a = effect2.onTrigger) == null ? void 0 : _a.call(effect2, extend({ effect: effect2 }, debuggerEventExtraInfo));      }      effect2.trigger();      if ((!effect2._runnings || effect2.allowRecurse) && effect2._dirtyLevel !== 2) {        effect2._shouldSchedule = false;        if (effect2.scheduler) {          queueEffectSchedulers.push(effect2.scheduler);        }      }    }  }  resetScheduling();}function pauseScheduling() {  pauseScheduleStack++;}

function resetScheduling() {  pauseScheduleStack--;  while (!pauseScheduleStack && queueEffectSchedulers.length) {    queueEffectSchedulers.shift()();  }}




innerScopeA.run(() => {      const doubled = computed(() => count.value * 2);      watchEffect(() => {        console.log('[innerA] doubled =', doubled.value);      });      onScopeDispose(() => console.log('[innerA] cleanup'));    });    // 子 scope B:detached,不会随 outer 停止,需手动 stop    innerScopeB = effectScope(true);    innerScopeB.run(() => {      const tripled = computed(() => count.value * 3);      watchEffect(() => {        console.log('[innerB-detached] tripled =', tripled.value);      });      onScopeDispose(() => console.log('[innerB-detached] cleanup'));    });


function onScopeDispose(fn) {  if (activeEffectScope) {    activeEffectScope.cleanups.push(fn);  } else if (!!(process.env.NODE_ENV !== "production")) {    warn$2(      `onScopeDispose() is called when there is no active effect scope to be associated with.`    );  }}





简化理解

创建副作用域
effectScope();
new EffectScope(detached);转交控制权 
 const currentEffectScope = activeEffectScope;执行副作用域函数
 fn();
创建响应式副作用
   watchEffect(() => {      console.log('[outer] count =', count.value);    });doWatch(effect, null, options);
  const effect = new ReactiveEffect(getter, NOOP, scheduler);
收集副作用
    recordEffectScope(this,scope);

绑定副作用域清除
  const scope = getCurrentScope();const unwatch = () => {    effect.stop();    if (scope) {      remove(scope.effects, effect);    }  };  if (ssrCleanup)    ssrCleanup.push(unwatch);  return unwatch;
执行子副作用域流程
   const doubled = computed(() => count.value * 2);   const c = computed$1(getterOrOptions, debugOptions, isInSSRComponentSetup);
   const cRef = new ComputedRefImpl(getter, setter, onlyGetter || !setter, isSSR);  class ComputedRefImpl {  constructor(getter, _setter, isReadonly, isSSR) {    this.getter = getter;    this._setter = _setter;    this.dep = void 0;    this.__v_isRef = true;    this["__v_isReadonly"] = false;    this.effect = new ReactiveEffect(      () => getter(this._value),      () => triggerRefValue(        this,        this.effect._dirtyLevel === 2 ? 2 : 3      )    );


收集作用域名清除函数
   onScopeDispose(() => console.log('[outer] cleanup'));function onScopeDispose(fn) {  if (activeEffectScope) {    activeEffectScope.cleanups.push(fn);  } else if (!!(process.env.NODE_ENV !== "production")) {    warn$2(      `onScopeDispose() is called when there is no active effect scope to be associated with.`    );  }}