Skip to content

Commit

Permalink
feat(reactivity): onEffectCleanup API
Browse files Browse the repository at this point in the history
ref #10173

Instead of exposing `getCurrentEffect`, this version accepts a second
argument to suppress the no-active-effect warning.
  • Loading branch information
yyx990803 committed Mar 5, 2024
1 parent 70196a4 commit 2cc5615
Show file tree
Hide file tree
Showing 3 changed files with 84 additions and 0 deletions.
39 changes: 39 additions & 0 deletions packages/reactivity/__tests__/effect.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
} from '@vue/runtime-test'
import {
endBatch,
onEffectCleanup,
pauseTracking,
resetTracking,
startBatch,
Expand Down Expand Up @@ -1131,4 +1132,42 @@ describe('reactivity/effect', () => {
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()
})
})
})
44 changes: 44 additions & 0 deletions packages/reactivity/src/effect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,10 @@ export class ReactiveEffect<T = any>
* @internal
*/
nextEffect?: ReactiveEffect = undefined
/**
* @internal
*/
cleanup?: () => void = undefined

scheduler?: EffectScheduler = undefined
onStop?: () => void
Expand Down Expand Up @@ -165,6 +169,7 @@ export class ReactiveEffect<T = any>
}

this.flags |= EffectFlags.RUNNING
cleanupEffect(this)
prepareDeps(this)
const prevEffect = activeSub
const prevShouldTrack = shouldTrack
Expand Down Expand Up @@ -193,6 +198,7 @@ export class ReactiveEffect<T = any>
removeSub(link)
}
this.deps = this.depsTail = undefined
cleanupEffect(this)
this.onStop && this.onStop()
this.flags &= ~EffectFlags.ACTIVE
}
Expand Down Expand Up @@ -479,3 +485,41 @@ export function resetTracking() {
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
}
}
}
1 change: 1 addition & 0 deletions packages/reactivity/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ export {
enableTracking,
pauseTracking,
resetTracking,
onEffectCleanup,
ReactiveEffect,
EffectFlags,
type ReactiveEffectRunner,
Expand Down

0 comments on commit 2cc5615

Please sign in to comment.