Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(watch): accept watch callback return cleanup function #11664

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
79 changes: 79 additions & 0 deletions packages/reactivity/__tests__/watch.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -209,4 +209,83 @@ describe('watch', () => {
source.value++
expect(dummy).toBe(1)
})

test('watch callback return cleanup function', async () => {
const fn = vi.fn()

const scope = new EffectScope()

scope.run(() => {
const source = ref(0)
watch(source, () => fn)
source.value++
})

scope.stop()
await nextTick()

expect(fn).toBeCalledTimes(1)
})

test('watch async callback return cleanup function', async () => {
const fn = vi.fn()

const scope = new EffectScope()

scope.run(() => {
const source = ref(0)
watch(source, async () => fn)
source.value++
})

await nextTick()

scope.stop()
await nextTick()

expect(fn).toBeCalledTimes(1)
})

test('watch effect return cleanup function', async () => {
const fn = vi.fn()

const scope = new EffectScope()

scope.run(() => {
const source = ref(0)
watch(() => {
void source.value
return () => fn()
})
source.value++
})

await nextTick()
expect(fn).toBeCalledTimes(1)

scope.stop()
await nextTick()
expect(fn).toBeCalledTimes(2)
})

test('watch effect async callback return cleanup function', async () => {
const fn = vi.fn()

const scope = new EffectScope()

scope.run(() => {
const source = ref(0)
watch(async () => {
void source.value
return fn
})
})

await nextTick()

scope.stop()
await nextTick()

expect(fn).toBeCalledTimes(1)
})
})
30 changes: 27 additions & 3 deletions packages/reactivity/src/watch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
isMap,
isObject,
isPlainObject,
isPromise,
isSet,
remove,
} from '@vue/shared'
Expand Down Expand Up @@ -63,7 +64,7 @@ export interface WatchOptions<Immediate = boolean> extends DebuggerOptions {
fn: Function | Function[],
type: WatchErrorCodes,
args?: unknown[],
) => void
) => any
}

export type WatchStopHandle = () => void
Expand Down Expand Up @@ -191,9 +192,21 @@ export function watch(
const currentEffect = activeWatcher
activeWatcher = effect
try {
return call
const maybeCleanup = call
? call(source, WatchErrorCodes.WATCH_CALLBACK, [boundCleanup])
: source(boundCleanup)

if (isFunction(maybeCleanup)) {
boundCleanup(maybeCleanup)
} else if (isPromise(maybeCleanup)) {
maybeCleanup
.then(cleanup => {
if (isFunction(cleanup)) {
boundCleanup(cleanup)
}
})
.catch(NOOP)
}
} finally {
activeWatcher = currentEffect
}
Expand Down Expand Up @@ -264,10 +277,21 @@ export function watch(
: oldValue,
boundCleanup,
]
call
const maybeCleanup = call
? call(cb!, WatchErrorCodes.WATCH_CALLBACK, args)
: // @ts-expect-error
cb!(...args)
if (isFunction(maybeCleanup)) {
boundCleanup(maybeCleanup)
} else if (isPromise(maybeCleanup)) {
maybeCleanup
.then(cleanup => {
if (isFunction(cleanup)) {
boundCleanup(cleanup)
}
})
.catch(NOOP)
}
oldValue = newValue
} finally {
activeWatcher = currentWatcher
Expand Down