9
9
isReactive ,
10
10
isRef ,
11
11
isShallow ,
12
+ pauseTracking ,
13
+ resetTracking ,
12
14
} from '@vue/reactivity'
13
15
import { type SchedulerJob , queueJob } from './scheduler'
14
16
import {
@@ -169,6 +171,39 @@ export function watch<T = any, Immediate extends Readonly<boolean> = false>(
169
171
return doWatch ( source as any , cb , options )
170
172
}
171
173
174
+ const cleanupMap : WeakMap < ReactiveEffect , ( ( ) => void ) [ ] > = new WeakMap ( )
175
+ let activeEffect : ReactiveEffect | undefined = undefined
176
+
177
+ /**
178
+ * Returns the current active effect if there is one.
179
+ */
180
+ export function getCurrentEffect ( ) {
181
+ return activeEffect
182
+ }
183
+
184
+ /**
185
+ * Registers a cleanup callback on the current active effect. This
186
+ * registered cleanup callback will be invoked right before the
187
+ * associated effect re-runs.
188
+ *
189
+ * @param cleanupFn - The callback function to attach to the effect's cleanup.
190
+ */
191
+ export function onEffectCleanup ( cleanupFn : ( ) => void ) {
192
+ // in SSR there is no need to call the invalidate callback
193
+ if ( __SSR__ && isInSSRComponentSetup ) return
194
+ if ( activeEffect ) {
195
+ const cleanups =
196
+ cleanupMap . get ( activeEffect ) ||
197
+ cleanupMap . set ( activeEffect , [ ] ) . get ( activeEffect ) !
198
+ cleanups . push ( cleanupFn )
199
+ } else if ( __DEV__ ) {
200
+ warn (
201
+ `onEffectCleanup() was called when there was no active effect` +
202
+ ` to associate with.` ,
203
+ )
204
+ }
205
+ }
206
+
172
207
function doWatch (
173
208
source : WatchSource | WatchSource [ ] | WatchEffect | object ,
174
209
cb : WatchCallback | null ,
@@ -235,6 +270,7 @@ function doWatch(
235
270
traverse ( source , deep === false ? 1 : undefined )
236
271
237
272
let getter : ( ) => any
273
+ let cleanup : ( ( ) => void ) | undefined
238
274
let forceTrigger = false
239
275
let isMultiSource = false
240
276
@@ -268,14 +304,25 @@ function doWatch(
268
304
// no cb -> simple effect
269
305
getter = ( ) => {
270
306
if ( cleanup ) {
271
- cleanup ( )
307
+ pauseTracking ( )
308
+ try {
309
+ cleanup ( )
310
+ } finally {
311
+ resetTracking ( )
312
+ }
313
+ }
314
+ const currentEffect = activeEffect
315
+ activeEffect = effect
316
+ try {
317
+ return callWithAsyncErrorHandling (
318
+ source ,
319
+ instance ,
320
+ ErrorCodes . WATCH_CALLBACK ,
321
+ [ onEffectCleanup ] ,
322
+ )
323
+ } finally {
324
+ activeEffect = currentEffect
272
325
}
273
- return callWithAsyncErrorHandling (
274
- source ,
275
- instance ,
276
- ErrorCodes . WATCH_CALLBACK ,
277
- [ onCleanup ] ,
278
- )
279
326
}
280
327
}
281
328
} else {
@@ -303,27 +350,17 @@ function doWatch(
303
350
getter = ( ) => traverse ( baseGetter ( ) )
304
351
}
305
352
306
- let cleanup : ( ( ) => void ) | undefined
307
- let onCleanup : OnCleanup = ( fn : ( ) => void ) => {
308
- cleanup = effect . onStop = ( ) => {
309
- callWithErrorHandling ( fn , instance , ErrorCodes . WATCH_CLEANUP )
310
- cleanup = effect . onStop = undefined
311
- }
312
- }
313
-
314
353
// in SSR there is no need to setup an actual effect, and it should be noop
315
354
// unless it's eager or sync flush
316
355
let ssrCleanup : ( ( ) => void ) [ ] | undefined
317
356
if ( __SSR__ && isInSSRComponentSetup ) {
318
- // we will also not call the invalidate callback (+ runner is not set up)
319
- onCleanup = NOOP
320
357
if ( ! cb ) {
321
358
getter ( )
322
359
} else if ( immediate ) {
323
360
callWithAsyncErrorHandling ( cb , instance , ErrorCodes . WATCH_CALLBACK , [
324
361
getter ( ) ,
325
362
isMultiSource ? [ ] : undefined ,
326
- onCleanup ,
363
+ onEffectCleanup ,
327
364
] )
328
365
}
329
366
if ( flush === 'sync' ) {
@@ -358,16 +395,22 @@ function doWatch(
358
395
if ( cleanup ) {
359
396
cleanup ( )
360
397
}
361
- callWithAsyncErrorHandling ( cb , instance , ErrorCodes . WATCH_CALLBACK , [
362
- newValue ,
363
- // pass undefined as the old value when it's changed for the first time
364
- oldValue === INITIAL_WATCHER_VALUE
365
- ? undefined
366
- : isMultiSource && oldValue [ 0 ] === INITIAL_WATCHER_VALUE
367
- ? [ ]
368
- : oldValue ,
369
- onCleanup ,
370
- ] )
398
+ const currentEffect = activeEffect
399
+ activeEffect = effect
400
+ try {
401
+ callWithAsyncErrorHandling ( cb , instance , ErrorCodes . WATCH_CALLBACK , [
402
+ newValue ,
403
+ // pass undefined as the old value when it's changed for the first time
404
+ oldValue === INITIAL_WATCHER_VALUE
405
+ ? undefined
406
+ : isMultiSource && oldValue [ 0 ] === INITIAL_WATCHER_VALUE
407
+ ? [ ]
408
+ : oldValue ,
409
+ onEffectCleanup ,
410
+ ] )
411
+ } finally {
412
+ activeEffect = currentEffect
413
+ }
371
414
oldValue = newValue
372
415
}
373
416
} else {
@@ -394,6 +437,16 @@ function doWatch(
394
437
395
438
const effect = new ReactiveEffect ( getter , NOOP , scheduler )
396
439
440
+ cleanup = effect . onStop = ( ) => {
441
+ const cleanups = cleanupMap . get ( effect )
442
+ if ( cleanups ) {
443
+ cleanups . forEach ( cleanup =>
444
+ callWithErrorHandling ( cleanup , instance , ErrorCodes . WATCH_CLEANUP ) ,
445
+ )
446
+ cleanupMap . delete ( effect )
447
+ }
448
+ }
449
+
397
450
const scope = getCurrentScope ( )
398
451
const unwatch = ( ) => {
399
452
effect . stop ( )
0 commit comments