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 ,
@@ -234,7 +269,9 @@ function doWatch(
234
269
: // for deep: false, only traverse root-level properties
235
270
traverse ( source , deep === false ? 1 : undefined )
236
271
272
+ let effect : ReactiveEffect
237
273
let getter : ( ) => any
274
+ let cleanup : ( ( ) => void ) | undefined
238
275
let forceTrigger = false
239
276
let isMultiSource = false
240
277
@@ -268,14 +305,25 @@ function doWatch(
268
305
// no cb -> simple effect
269
306
getter = ( ) => {
270
307
if ( cleanup ) {
271
- cleanup ( )
308
+ pauseTracking ( )
309
+ try {
310
+ cleanup ( )
311
+ } finally {
312
+ resetTracking ( )
313
+ }
314
+ }
315
+ const currentEffect = activeEffect
316
+ activeEffect = effect
317
+ try {
318
+ return callWithAsyncErrorHandling (
319
+ source ,
320
+ instance ,
321
+ ErrorCodes . WATCH_CALLBACK ,
322
+ [ onEffectCleanup ] ,
323
+ )
324
+ } finally {
325
+ activeEffect = currentEffect
272
326
}
273
- return callWithAsyncErrorHandling (
274
- source ,
275
- instance ,
276
- ErrorCodes . WATCH_CALLBACK ,
277
- [ onCleanup ] ,
278
- )
279
327
}
280
328
}
281
329
} else {
@@ -303,27 +351,17 @@ function doWatch(
303
351
getter = ( ) => traverse ( baseGetter ( ) )
304
352
}
305
353
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
354
// in SSR there is no need to setup an actual effect, and it should be noop
315
355
// unless it's eager or sync flush
316
356
let ssrCleanup : ( ( ) => void ) [ ] | undefined
317
357
if ( __SSR__ && isInSSRComponentSetup ) {
318
- // we will also not call the invalidate callback (+ runner is not set up)
319
- onCleanup = NOOP
320
358
if ( ! cb ) {
321
359
getter ( )
322
360
} else if ( immediate ) {
323
361
callWithAsyncErrorHandling ( cb , instance , ErrorCodes . WATCH_CALLBACK , [
324
362
getter ( ) ,
325
363
isMultiSource ? [ ] : undefined ,
326
- onCleanup ,
364
+ onEffectCleanup ,
327
365
] )
328
366
}
329
367
if ( flush === 'sync' ) {
@@ -358,16 +396,22 @@ function doWatch(
358
396
if ( cleanup ) {
359
397
cleanup ( )
360
398
}
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
- ] )
399
+ const currentEffect = activeEffect
400
+ activeEffect = effect
401
+ try {
402
+ callWithAsyncErrorHandling ( cb , instance , ErrorCodes . WATCH_CALLBACK , [
403
+ newValue ,
404
+ // pass undefined as the old value when it's changed for the first time
405
+ oldValue === INITIAL_WATCHER_VALUE
406
+ ? undefined
407
+ : isMultiSource && oldValue [ 0 ] === INITIAL_WATCHER_VALUE
408
+ ? [ ]
409
+ : oldValue ,
410
+ onEffectCleanup ,
411
+ ] )
412
+ } finally {
413
+ activeEffect = currentEffect
414
+ }
371
415
oldValue = newValue
372
416
}
373
417
} else {
@@ -392,7 +436,17 @@ function doWatch(
392
436
scheduler = ( ) => queueJob ( job )
393
437
}
394
438
395
- const effect = new ReactiveEffect ( getter , NOOP , scheduler )
439
+ effect = new ReactiveEffect ( getter , NOOP , scheduler )
440
+
441
+ cleanup = effect . onStop = ( ) => {
442
+ const cleanups = cleanupMap . get ( effect )
443
+ if ( cleanups ) {
444
+ cleanups . forEach ( cleanup =>
445
+ callWithErrorHandling ( cleanup , instance , ErrorCodes . WATCH_CLEANUP ) ,
446
+ )
447
+ cleanupMap . delete ( effect )
448
+ }
449
+ }
396
450
397
451
const scope = getCurrentScope ( )
398
452
const unwatch = ( ) => {
0 commit comments