Skip to content

Commit 4676188

Browse files
committed
refactor(baseWatch): rename onEffectCleanup to onWatcherCleanup and getCurrentEffect to getCurrentWatcher for clarity
1 parent db4040d commit 4676188

File tree

10 files changed

+121
-81
lines changed

10 files changed

+121
-81
lines changed

packages/reactivity/__tests__/baseWatch.spec.ts

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {
55
type SchedulerJob,
66
type WatchScheduler,
77
baseWatch,
8-
onEffectCleanup,
8+
onWatcherCleanup,
99
ref,
1010
} from '../src'
1111

@@ -72,7 +72,7 @@ describe('baseWatch', () => {
7272
const effect = baseWatch(
7373
source,
7474
() => {
75-
onEffectCleanup(() => {
75+
onWatcherCleanup(() => {
7676
throw 'oops in cleanup'
7777
})
7878
throw 'oops in watch'
@@ -102,7 +102,7 @@ describe('baseWatch', () => {
102102
])
103103
})
104104

105-
test('baseWatch with onEffectCleanup', async () => {
105+
test('baseWatch with onWatcherCleanup', async () => {
106106
let dummy = 0
107107
let source: Ref<number>
108108
const scope = new EffectScope()
@@ -113,8 +113,8 @@ describe('baseWatch', () => {
113113
source.value
114114

115115
onCleanup(() => (dummy += 2))
116-
onEffectCleanup(() => (dummy += 3))
117-
onEffectCleanup(() => (dummy += 5))
116+
onWatcherCleanup(() => (dummy += 3))
117+
onWatcherCleanup(() => (dummy += 5))
118118
})
119119
})
120120
expect(dummy).toBe(0)
@@ -133,7 +133,7 @@ describe('baseWatch', () => {
133133
expect(dummy).toBe(30)
134134
})
135135

136-
test('nested calls to baseWatch and onEffectCleanup', async () => {
136+
test('nested calls to baseWatch and onWatcherCleanup', async () => {
137137
let calls: string[] = []
138138
let source: Ref<number>
139139
let copyist: Ref<number>
@@ -146,7 +146,7 @@ describe('baseWatch', () => {
146146
baseWatch(
147147
() => {
148148
const current = (copyist.value = source.value)
149-
onEffectCleanup(() => calls.push(`sync ${current}`))
149+
onWatcherCleanup(() => calls.push(`sync ${current}`))
150150
},
151151
null,
152152
{},
@@ -155,7 +155,7 @@ describe('baseWatch', () => {
155155
baseWatch(
156156
() => {
157157
const current = copyist.value
158-
onEffectCleanup(() => calls.push(`post ${current}`))
158+
onWatcherCleanup(() => calls.push(`post ${current}`))
159159
},
160160
null,
161161
{ scheduler },
@@ -180,6 +180,7 @@ describe('baseWatch', () => {
180180
scope.stop()
181181
expect(calls).toEqual(['sync 2', 'post 2'])
182182
})
183+
183184
test('baseWatch with middleware', async () => {
184185
let effectCalls: string[] = []
185186
let watchCalls: string[] = []
@@ -190,7 +191,7 @@ describe('baseWatch', () => {
190191
() => {
191192
source.value
192193
effectCalls.push('effect')
193-
onEffectCleanup(() => effectCalls.push('effect cleanup'))
194+
onWatcherCleanup(() => effectCalls.push('effect cleanup'))
194195
},
195196
null,
196197
{
@@ -207,7 +208,7 @@ describe('baseWatch', () => {
207208
() => source.value,
208209
() => {
209210
watchCalls.push('watch')
210-
onEffectCleanup(() => watchCalls.push('watch cleanup'))
211+
onWatcherCleanup(() => watchCalls.push('watch cleanup'))
211212
},
212213
{
213214
scheduler,

packages/reactivity/src/baseWatch.ts

Lines changed: 51 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import type { ComputedRef } from './computed'
1515
import { ReactiveFlags } from './constants'
1616
import {
1717
type DebuggerOptions,
18-
type EffectScheduler,
18+
EffectFlags,
1919
ReactiveEffect,
2020
pauseTracking,
2121
resetTracking,
@@ -46,7 +46,7 @@ export interface BaseWatchOptions<Immediate = boolean> extends DebuggerOptions {
4646
immediate?: Immediate
4747
deep?: boolean
4848
once?: boolean
49-
scheduler?: Scheduler
49+
scheduler?: WatchScheduler
5050
middleware?: BaseWatchMiddleware
5151
onError?: HandleError
5252
onWarn?: HandleWarn
@@ -55,22 +55,41 @@ export interface BaseWatchOptions<Immediate = boolean> extends DebuggerOptions {
5555
// initial value for watchers to trigger on undefined initial values
5656
const INITIAL_WATCHER_VALUE = {}
5757

58-
export type Scheduler = (
58+
export type WatchScheduler = (
5959
job: SchedulerJob,
6060
effect: ReactiveEffect,
61-
isInit: boolean,
61+
immediateFirstRun: boolean,
62+
hasCb: boolean,
6263
) => void
6364
export type BaseWatchMiddleware = (next: () => unknown) => any
6465
export type HandleError = (err: unknown, type: BaseWatchErrorCodes) => void
6566
export type HandleWarn = (msg: string, ...args: any[]) => void
6667

67-
const DEFAULT_SCHEDULER: Scheduler = job => job()
68+
const DEFAULT_SCHEDULER: WatchScheduler = (
69+
job,
70+
effect,
71+
immediateFirstRun,
72+
hasCb,
73+
) => {
74+
if (immediateFirstRun) {
75+
!hasCb && effect.run()
76+
} else {
77+
job()
78+
}
79+
}
6880
const DEFAULT_HANDLE_ERROR: HandleError = (err: unknown) => {
6981
throw err
7082
}
7183

7284
const cleanupMap: WeakMap<ReactiveEffect, (() => void)[]> = new WeakMap()
73-
let activeEffect: ReactiveEffect | undefined = undefined
85+
let activeWatcher: ReactiveEffect | undefined = undefined
86+
87+
/**
88+
* Returns the current active effect if there is one.
89+
*/
90+
export function getCurrentWatcher() {
91+
return activeWatcher
92+
}
7493

7594
/**
7695
* Registers a cleanup callback on the current active effect. This
@@ -79,15 +98,15 @@ let activeEffect: ReactiveEffect | undefined = undefined
7998
*
8099
* @param cleanupFn - The callback function to attach to the effect's cleanup.
81100
*/
82-
export function onEffectCleanup(cleanupFn: () => void) {
83-
if (activeEffect) {
101+
export function onWatcherCleanup(cleanupFn: () => void, failSilently = false) {
102+
if (activeWatcher) {
84103
const cleanups =
85-
cleanupMap.get(activeEffect) ||
86-
cleanupMap.set(activeEffect, []).get(activeEffect)!
104+
cleanupMap.get(activeWatcher) ||
105+
cleanupMap.set(activeWatcher, []).get(activeWatcher)!
87106
cleanups.push(cleanupFn)
88-
} else if (__DEV__) {
107+
} else if (__DEV__ && !failSilently) {
89108
warn(
90-
`onEffectCleanup() was called when there was no active effect` +
109+
`onWatcherCleanup() was called when there was no active watcher` +
91110
` to associate with.`,
92111
)
93112
}
@@ -170,17 +189,17 @@ export function baseWatch(
170189
resetTracking()
171190
}
172191
}
173-
const currentEffect = activeEffect
174-
activeEffect = effect
192+
const currentEffect = activeWatcher
193+
activeWatcher = effect
175194
try {
176195
return callWithAsyncErrorHandling(
177196
source,
178197
onError,
179198
BaseWatchErrorCodes.WATCH_CALLBACK,
180-
[onEffectCleanup],
199+
[onWatcherCleanup],
181200
)
182201
} finally {
183-
activeEffect = currentEffect
202+
activeWatcher = currentEffect
184203
}
185204
}
186205
if (middleware) {
@@ -198,30 +217,19 @@ export function baseWatch(
198217
getter = () => traverse(baseGetter())
199218
}
200219

201-
const scope = getCurrentScope()
202-
203220
if (once) {
204-
if (!cb) {
205-
// onEffectCleanup need use effect as a key
206-
scope?.effects.push((effect = {} as any))
207-
getter()
208-
return
209-
}
210-
if (immediate) {
211-
// onEffectCleanup need use effect as a key
212-
scope?.effects.push((effect = {} as any))
213-
callWithAsyncErrorHandling(
214-
cb,
215-
onError,
216-
BaseWatchErrorCodes.WATCH_CALLBACK,
217-
[getter(), isMultiSource ? [] : undefined, onEffectCleanup],
218-
)
219-
return
220-
}
221-
const _cb = cb
222-
cb = (...args) => {
223-
_cb(...args)
224-
effect?.stop()
221+
if (cb) {
222+
const _cb = cb
223+
cb = (...args) => {
224+
_cb(...args)
225+
effect?.stop()
226+
}
227+
} else {
228+
const _getter = getter
229+
getter = () => {
230+
_getter()
231+
effect?.stop()
232+
}
225233
}
226234
}
227235

@@ -250,8 +258,8 @@ export function baseWatch(
250258
if (cleanup) {
251259
cleanup()
252260
}
253-
const currentEffect = activeEffect
254-
activeEffect = effect
261+
const currentWatcher = activeWatcher
262+
activeWatcher = effect
255263
try {
256264
callWithAsyncErrorHandling(
257265
cb!,
@@ -265,12 +273,12 @@ export function baseWatch(
265273
: isMultiSource && oldValue[0] === INITIAL_WATCHER_VALUE
266274
? []
267275
: oldValue,
268-
onEffectCleanup,
276+
onWatcherCleanup,
269277
],
270278
)
271279
oldValue = newValue
272280
} finally {
273-
activeEffect = currentEffect
281+
activeWatcher = currentWatcher
274282
}
275283
}
276284
if (middleware) {

packages/reactivity/src/index.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,11 +80,12 @@ export { reactiveReadArray, shallowReadArray } from './arrayInstrumentations'
8080
export { TrackOpTypes, TriggerOpTypes, ReactiveFlags } from './constants'
8181
export {
8282
baseWatch,
83-
onEffectCleanup,
83+
getCurrentWatcher,
8484
traverse,
85+
onWatcherCleanup,
8586
BaseWatchErrorCodes,
8687
type BaseWatchOptions,
8788
type BaseWatchMiddleware,
88-
type Scheduler,
89-
type SchedulerJob,
89+
type WatchScheduler,
9090
} from './baseWatch'
91+
export { type SchedulerJob, SchedulerJobFlags } from './scheduler'

packages/reactivity/src/scheduler.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
export enum SchedulerJobFlags {
2+
QUEUED = 1 << 0,
3+
PRE = 1 << 1,
4+
/**
5+
* Indicates whether the effect is allowed to recursively trigger itself
6+
* when managed by the scheduler.
7+
*
8+
* By default, a job cannot trigger itself because some built-in method calls,
9+
* e.g. Array.prototype.push actually performs reads as well (#1740) which
10+
* can lead to confusing infinite loops.
11+
* The allowed cases are component update functions and watch callbacks.
12+
* Component update functions may update child component props, which in turn
13+
* trigger flush: "pre" watch callbacks that mutates state that the parent
14+
* relies on (#1801). Watch callbacks doesn't track its dependencies so if it
15+
* triggers itself again, it's likely intentional and it is the user's
16+
* responsibility to perform recursive state mutation that eventually
17+
* stabilizes (#1727).
18+
*/
19+
ALLOW_RECURSE = 1 << 2,
20+
DISPOSED = 1 << 3,
21+
}
22+
23+
export interface SchedulerJob extends Function {
24+
id?: number
25+
/**
26+
* flags can technically be undefined, but it can still be used in bitwise
27+
* operations just like 0.
28+
*/
29+
flags?: SchedulerJobFlags
30+
}

packages/runtime-core/__tests__/apiWatch.spec.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {
55
defineComponent,
66
getCurrentInstance,
77
nextTick,
8-
onEffectCleanup,
8+
onWatcherCleanup,
99
reactive,
1010
ref,
1111
watch,
@@ -395,17 +395,17 @@ describe('api: watch', () => {
395395
expect(cleanup).toHaveBeenCalledTimes(2)
396396
})
397397

398-
it('onEffectCleanup', async () => {
398+
it('onWatcherCleanup', async () => {
399399
const count = ref(0)
400400
const cleanupEffect = vi.fn()
401401
const cleanupWatch = vi.fn()
402402

403403
const stopEffect = watchEffect(() => {
404-
onEffectCleanup(cleanupEffect)
404+
onWatcherCleanup(cleanupEffect)
405405
count.value
406406
})
407407
const stopWatch = watch(count, () => {
408-
onEffectCleanup(cleanupWatch)
408+
onWatcherCleanup(cleanupWatch)
409409
})
410410

411411
count.value++

packages/runtime-core/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ export {
2828
// effect
2929
effect,
3030
stop,
31-
onEffectCleanup,
31+
onWatcherCleanup,
3232
ReactiveEffect,
3333
// effect scope
3434
effectScope,

packages/runtime-vapor/__tests__/apiWatch.spec.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@ import type { Ref } from '@vue/reactivity'
22
import {
33
EffectScope,
44
nextTick,
5-
onEffectCleanup,
5+
onWatcherCleanup,
66
ref,
77
watchEffect,
88
watchSyncEffect,
99
} from '../src'
1010

11-
describe('watchEffect and onEffectCleanup', () => {
11+
describe('watchEffect and onWatcherCleanup', () => {
1212
test('basic', async () => {
1313
let dummy = 0
1414
let source: Ref<number>
@@ -20,8 +20,8 @@ describe('watchEffect and onEffectCleanup', () => {
2020
source.value
2121

2222
onCleanup(() => (dummy += 2))
23-
onEffectCleanup(() => (dummy += 3))
24-
onEffectCleanup(() => (dummy += 5))
23+
onWatcherCleanup(() => (dummy += 3))
24+
onWatcherCleanup(() => (dummy += 5))
2525
})
2626
})
2727
await nextTick()
@@ -55,11 +55,11 @@ describe('watchEffect and onEffectCleanup', () => {
5555
double = ref(0)
5656
watchEffect(() => {
5757
double.value = source.value * 2
58-
onEffectCleanup(() => (dummy += 2))
58+
onWatcherCleanup(() => (dummy += 2))
5959
})
6060
watchSyncEffect(() => {
6161
double.value
62-
onEffectCleanup(() => (dummy += 3))
62+
onWatcherCleanup(() => (dummy += 3))
6363
})
6464
})
6565
await nextTick()

0 commit comments

Comments
 (0)