} from '@vue/runtime-test'
import {
endBatch,
+ onEffectCleanup,
pauseTracking,
resetTracking,
startBatch,
expect(getSubCount(depC)).toBe(1)
})
})
+
+ describe('onEffectCleanup', () => {
+ it('should get called correctly', async () => {
+ const count = ref(0)
+ const cleanupEffect = vi.fn()
+
+ const e = effect(() => {
+ onEffectCleanup(cleanupEffect)
+ count.value
+ })
+
+ count.value++
+ await nextTick()
+ expect(cleanupEffect).toHaveBeenCalledTimes(1)
+
+ count.value++
+ await nextTick()
+ expect(cleanupEffect).toHaveBeenCalledTimes(2)
+
+ // call it on stop
+ e.effect.stop()
+ expect(cleanupEffect).toHaveBeenCalledTimes(3)
+ })
+
+ it('should warn if called without active effect', () => {
+ onEffectCleanup(() => {})
+ expect(
+ `onEffectCleanup() was called when there was no active effect`,
+ ).toHaveBeenWarned()
+ })
+
+ it('should not warn without active effect when failSilently argument is passed', () => {
+ onEffectCleanup(() => {}, true)
+ expect(
+ `onEffectCleanup() was called when there was no active effect`,
+ ).not.toHaveBeenWarned()
+ })
+ })
})
* @internal
*/
nextEffect?: ReactiveEffect = undefined
+ /**
+ * @internal
+ */
+ cleanup?: () => void = undefined
scheduler?: EffectScheduler = undefined
onStop?: () => void
}
this.flags |= EffectFlags.RUNNING
+ cleanupEffect(this)
prepareDeps(this)
const prevEffect = activeSub
const prevShouldTrack = shouldTrack
removeSub(link)
}
this.deps = this.depsTail = undefined
+ cleanupEffect(this)
this.onStop && this.onStop()
this.flags &= ~EffectFlags.ACTIVE
}
const last = trackStack.pop()
shouldTrack = last === undefined ? true : last
}
+
+/**
+ * Registers a cleanup function for the current active effect.
+ * The cleanup function is called right before the next effect run, or when the
+ * effect is stopped.
+ *
+ * Throws a warning iff there is no currenct active effect. The warning can be
+ * suppressed by passing `true` to the second argument.
+ *
+ * @param fn - the cleanup function to be registered
+ * @param failSilently - if `true`, will not throw warning when called without
+ * an active effect.
+ */
+export function onEffectCleanup(fn: () => void, failSilently = false) {
+ if (activeSub instanceof ReactiveEffect) {
+ activeSub.cleanup = fn
+ } else if (__DEV__ && !failSilently) {
+ warn(
+ `onEffectCleanup() was called when there was no active effect` +
+ ` to associate with.`,
+ )
+ }
+}
+
+function cleanupEffect(e: ReactiveEffect) {
+ const { cleanup } = e
+ e.cleanup = undefined
+ if (cleanup) {
+ // run cleanup without active effect
+ const prevSub = activeSub
+ activeSub = undefined
+ try {
+ cleanup()
+ } finally {
+ activeSub = prevSub
+ }
+ }
+}