Skip to content

Commit ee1a7ab

Browse files
committed
refactor: make effect scope enable/disable logic symmetric
1 parent 481df0e commit ee1a7ab

16 files changed

+66
-123
lines changed

packages/reactivity/__tests__/effectScope.spec.ts

+2-15
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
onScopeDispose,
1010
reactive,
1111
ref,
12+
setCurrentScope,
1213
} from '../src'
1314

1415
describe('reactivity/effect/scope', () => {
@@ -289,21 +290,7 @@ describe('reactivity/effect/scope', () => {
289290

290291
parentScope.run(() => {
291292
const childScope = effectScope(true)
292-
childScope.on()
293-
childScope.off()
294-
expect(getCurrentScope()).toBe(parentScope)
295-
})
296-
})
297-
298-
it('calling on() and off() multiple times inside an active scope should not break currentScope', () => {
299-
const parentScope = effectScope()
300-
parentScope.run(() => {
301-
const childScope = effectScope(true)
302-
childScope.on()
303-
childScope.on()
304-
childScope.off()
305-
childScope.off()
306-
childScope.off()
293+
setCurrentScope(setCurrentScope(childScope))
307294
expect(getCurrentScope()).toBe(parentScope)
308295
})
309296
})

packages/reactivity/src/effectScope.ts

+8-27
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,6 @@ export class EffectScope implements Subscriber, Dependency {
2020
subs: Link | undefined = undefined
2121
subsTail: Link | undefined = undefined
2222

23-
/**
24-
* @internal track `on` calls, allow `on` call multiple times
25-
*/
26-
private _on = 0
2723
/**
2824
* @internal
2925
*/
@@ -87,29 +83,6 @@ export class EffectScope implements Subscriber, Dependency {
8783
}
8884
}
8985

90-
prevScope: EffectScope | undefined
91-
/**
92-
* This should only be called on non-detached scopes
93-
* @internal
94-
*/
95-
on(): void {
96-
if (++this._on === 1) {
97-
this.prevScope = activeEffectScope
98-
activeEffectScope = this
99-
}
100-
}
101-
102-
/**
103-
* This should only be called on non-detached scopes
104-
* @internal
105-
*/
106-
off(): void {
107-
if (this._on > 0 && --this._on === 0) {
108-
activeEffectScope = this.prevScope
109-
this.prevScope = undefined
110-
}
111-
}
112-
11386
stop(): void {
11487
if (this.active) {
11588
this.flags |= EffectFlags.STOP
@@ -150,6 +123,14 @@ export function getCurrentScope(): EffectScope | undefined {
150123
return activeEffectScope
151124
}
152125

126+
export function setCurrentScope(
127+
scope: EffectScope | undefined,
128+
): EffectScope | undefined {
129+
const prevScope = activeEffectScope
130+
activeEffectScope = scope
131+
return prevScope
132+
}
133+
153134
/**
154135
* Registers a dispose callback on the current active effect scope. The
155136
* callback will be invoked when the associated effect scope is stopped.

packages/reactivity/src/index.ts

+4
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,10 @@ export {
7676
effectScope,
7777
EffectScope,
7878
getCurrentScope,
79+
/**
80+
* @internal
81+
*/
82+
setCurrentScope,
7983
onScopeDispose,
8084
} from './effectScope'
8185
export { reactiveReadArray, shallowReadArray } from './arrayInstrumentations'

packages/runtime-core/src/apiLifecycle.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,11 @@ export function injectHook(
3737
// Set currentInstance during hook invocation.
3838
// This assumes the hook does not synchronously trigger other hooks, which
3939
// can only be false when the user does something really funky.
40-
const reset = setCurrentInstance(target)
40+
const prev = setCurrentInstance(target)
4141
try {
4242
return callWithAsyncErrorHandling(hook, target, type, args)
4343
} finally {
44-
reset()
44+
setCurrentInstance(...prev)
4545
resetTracking()
4646
}
4747
})

packages/runtime-core/src/apiSetupHelpers.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import {
1414
createSetupContext,
1515
getCurrentGenericInstance,
1616
setCurrentInstance,
17-
unsetCurrentInstance,
1817
} from './component'
1918
import type { EmitFn, EmitsOptions, ObjectEmitsOptions } from './componentEmits'
2019
import type {
@@ -511,7 +510,7 @@ export function withAsyncContext(getAwaitable: () => any): [any, () => void] {
511510
)
512511
}
513512
let awaitable = getAwaitable()
514-
unsetCurrentInstance()
513+
setCurrentInstance(null, undefined)
515514
if (isPromise(awaitable)) {
516515
awaitable = awaitable.catch(e => {
517516
setCurrentInstance(ctx)

packages/runtime-core/src/apiWatch.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -253,9 +253,9 @@ export function instanceWatch(
253253
cb = value.handler as Function
254254
options = value
255255
}
256-
const reset = setCurrentInstance(this)
256+
const prev = setCurrentInstance(this)
257257
const res = doWatch(getter, cb.bind(publicThis), options)
258-
reset()
258+
setCurrentInstance(...prev)
259259
return res
260260
}
261261

packages/runtime-core/src/component.ts

+7-5
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,6 @@ import type { RendererElement } from './renderer'
9797
import {
9898
setCurrentInstance,
9999
setInSSRSetupState,
100-
unsetCurrentInstance,
101100
} from './componentCurrentInstance'
102101

103102
export * from './componentCurrentInstance'
@@ -891,7 +890,7 @@ function setupStatefulComponent(
891890
pauseTracking()
892891
const setupContext = (instance.setupContext =
893892
setup.length > 1 ? createSetupContext(instance) : null)
894-
const reset = setCurrentInstance(instance)
893+
const prev = setCurrentInstance(instance)
895894
const setupResult = callWithErrorHandling(
896895
setup,
897896
instance,
@@ -903,14 +902,17 @@ function setupStatefulComponent(
903902
)
904903
const isAsyncSetup = isPromise(setupResult)
905904
resetTracking()
906-
reset()
905+
setCurrentInstance(...prev)
907906

908907
if ((isAsyncSetup || instance.sp) && !isAsyncWrapper(instance)) {
909908
// async setup / serverPrefetch, mark as async boundary for useId()
910909
markAsyncBoundary(instance)
911910
}
912911

913912
if (isAsyncSetup) {
913+
const unsetCurrentInstance = (): void => {
914+
setCurrentInstance(null, undefined)
915+
}
914916
setupResult.then(unsetCurrentInstance, unsetCurrentInstance)
915917
if (isSSR) {
916918
// return the promise so server-renderer can wait on it
@@ -1083,13 +1085,13 @@ export function finishComponentSetup(
10831085

10841086
// support for 2.x options
10851087
if (__FEATURE_OPTIONS_API__ && !(__COMPAT__ && skipOptions)) {
1086-
const reset = setCurrentInstance(instance)
1088+
const prev = setCurrentInstance(instance)
10871089
pauseTracking()
10881090
try {
10891091
applyOptions(instance)
10901092
} finally {
10911093
resetTracking()
1092-
reset()
1094+
setCurrentInstance(...prev)
10931095
}
10941096
}
10951097

packages/runtime-core/src/componentCurrentInstance.ts

+16-32
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import type {
44
GenericComponentInstance,
55
} from './component'
66
import { currentRenderingInstance } from './componentRenderContext'
7+
import { type EffectScope, setCurrentScope } from '@vue/reactivity'
78

89
/**
910
* @internal
@@ -25,7 +26,10 @@ export let isInSSRComponentSetup = false
2526

2627
export let setInSSRSetupState: (state: boolean) => void
2728

28-
let internalSetCurrentInstance: (
29+
/**
30+
* @internal
31+
*/
32+
export let simpleSetCurrentInstance: (
2933
instance: GenericComponentInstance | null,
3034
) => void
3135

@@ -53,7 +57,7 @@ if (__SSR__) {
5357
else setters[0](v)
5458
}
5559
}
56-
internalSetCurrentInstance = registerGlobalSetter(
60+
simpleSetCurrentInstance = registerGlobalSetter(
5761
`__VUE_INSTANCE_SETTERS__`,
5862
v => (currentInstance = v),
5963
)
@@ -66,42 +70,22 @@ if (__SSR__) {
6670
v => (isInSSRComponentSetup = v),
6771
)
6872
} else {
69-
internalSetCurrentInstance = i => {
73+
simpleSetCurrentInstance = i => {
7074
currentInstance = i
7175
}
7276
setInSSRSetupState = v => {
7377
isInSSRComponentSetup = v
7478
}
7579
}
7680

77-
export const setCurrentInstance = (instance: GenericComponentInstance) => {
81+
export const setCurrentInstance = (
82+
instance: GenericComponentInstance | null,
83+
scope: EffectScope | undefined = instance !== null
84+
? instance.scope
85+
: undefined,
86+
): [GenericComponentInstance | null, EffectScope | undefined] => {
7887
const prev = currentInstance
79-
internalSetCurrentInstance(instance)
80-
instance.scope.on()
81-
return (): void => {
82-
instance.scope.off()
83-
internalSetCurrentInstance(prev)
84-
}
85-
}
86-
87-
export const unsetCurrentInstance = (): void => {
88-
currentInstance && currentInstance.scope.off()
89-
internalSetCurrentInstance(null)
90-
}
91-
92-
/**
93-
* Exposed for vapor only. Vapor never runs during SSR so we don't want to pay
94-
* for the extra overhead
95-
* @internal
96-
*/
97-
export const simpleSetCurrentInstance = (
98-
i: GenericComponentInstance | null,
99-
unset?: GenericComponentInstance | null,
100-
): void => {
101-
currentInstance = i
102-
if (unset) {
103-
unset.scope.off()
104-
} else if (i) {
105-
i.scope.on()
106-
}
88+
simpleSetCurrentInstance(instance)
89+
const prevScope = setCurrentScope(scope)
90+
return [prev, prevScope]
10791
}

packages/runtime-core/src/componentProps.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -522,15 +522,15 @@ function baseResolveDefault(
522522
key: string,
523523
) {
524524
let value
525-
const reset = setCurrentInstance(instance)
525+
const prev = setCurrentInstance(instance)
526526
const props = toRaw(instance.props)
527527
value = factory.call(
528528
__COMPAT__ && isCompatEnabled(DeprecationTypes.PROPS_DEFAULT_THIS, instance)
529529
? createPropsDefaultThis(instance, props, key)
530530
: null,
531531
props,
532532
)
533-
reset()
533+
setCurrentInstance(...prev)
534534
return value
535535
}
536536

packages/runtime-core/src/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -543,6 +543,7 @@ export {
543543
*/
544544
export {
545545
currentInstance,
546+
setCurrentInstance,
546547
simpleSetCurrentInstance,
547548
} from './componentCurrentInstance'
548549
/**

packages/runtime-core/src/renderer.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ import {
5959
ReactiveEffect,
6060
pauseTracking,
6161
resetTracking,
62+
setCurrentScope,
6263
} from '@vue/reactivity'
6364
import { updateProps } from './componentProps'
6465
import { updateSlots } from './componentSlots'
@@ -1594,9 +1595,9 @@ function baseCreateRenderer(
15941595
}
15951596

15961597
// create reactive effect for rendering
1597-
instance.scope.on()
1598+
const prevScope = setCurrentScope(instance.scope)
15981599
const effect = (instance.effect = new ReactiveEffect(componentUpdateFn))
1599-
instance.scope.off()
1600+
setCurrentScope(prevScope)
16001601

16011602
const update = (instance.update = effect.run.bind(effect))
16021603
const job: SchedulerJob = (instance.job = effect.scheduler.bind(effect))

packages/runtime-vapor/__tests__/dom/prop.spec.ts

+3-10
Original file line numberDiff line numberDiff line change
@@ -12,20 +12,13 @@ import {
1212
} from '../../src/dom/prop'
1313
import { setStyle } from '../../src/dom/prop'
1414
import { VaporComponentInstance } from '../../src/component'
15-
import {
16-
currentInstance,
17-
ref,
18-
simpleSetCurrentInstance,
19-
} from '@vue/runtime-dom'
15+
import { ref, setCurrentInstance } from '@vue/runtime-dom'
2016

2117
let removeComponentInstance = NOOP
2218
beforeEach(() => {
2319
const instance = new VaporComponentInstance({}, {}, null)
24-
const prev = currentInstance
25-
simpleSetCurrentInstance(instance)
26-
removeComponentInstance = () => {
27-
simpleSetCurrentInstance(prev)
28-
}
20+
const prev = setCurrentInstance(instance)
21+
removeComponentInstance = () => setCurrentInstance(...prev)
2922
})
3023
afterEach(() => {
3124
removeComponentInstance()

packages/runtime-vapor/src/component.ts

+3-4
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import {
2020
pushWarningContext,
2121
queuePostFlushCb,
2222
registerHMR,
23-
simpleSetCurrentInstance,
23+
setCurrentInstance,
2424
startMeasure,
2525
unregisterHMR,
2626
warn,
@@ -191,8 +191,7 @@ export function createComponent(
191191
instance.emitsOptions = normalizeEmitsOptions(component)
192192
}
193193

194-
const prev = currentInstance
195-
simpleSetCurrentInstance(instance)
194+
const prev = setCurrentInstance(instance)
196195
pauseTracking()
197196

198197
if (__DEV__) {
@@ -260,7 +259,7 @@ export function createComponent(
260259
}
261260

262261
resetTracking()
263-
simpleSetCurrentInstance(prev, instance)
262+
setCurrentInstance(...prev)
264263

265264
if (__DEV__) {
266265
popWarningContext()

packages/runtime-vapor/src/componentProps.ts

+3-5
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,11 @@ import type { VaporComponent, VaporComponentInstance } from './component'
1212
import {
1313
type NormalizedPropsOptions,
1414
baseNormalizePropsOptions,
15-
currentInstance,
1615
isEmitListener,
1716
popWarningContext,
1817
pushWarningContext,
1918
resolvePropValue,
20-
simpleSetCurrentInstance,
19+
setCurrentInstance,
2120
validateProps,
2221
warn,
2322
} from '@vue/runtime-dom'
@@ -257,10 +256,9 @@ function resolveDefault(
257256
factory: (props: Record<string, any>) => unknown,
258257
instance: VaporComponentInstance,
259258
) {
260-
const prev = currentInstance
261-
simpleSetCurrentInstance(instance)
259+
const prev = setCurrentInstance(instance)
262260
const res = factory.call(null, instance.props)
263-
simpleSetCurrentInstance(prev, instance)
261+
setCurrentInstance(...prev)
264262
return res
265263
}
266264

0 commit comments

Comments
 (0)