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.` ); }}