Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions packages/reactivity/__tests__/watch.spec.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { nextTick } from 'vue'
import {
EffectScope,
type Ref,
Expand Down Expand Up @@ -213,4 +214,25 @@ describe('watch', () => {
value.value = true
expect(value.value).toBe(false)
})

it('stop multiple watches by abort controller', async () => {
const controller = new AbortController()
const state = ref(0)
const cb1 = vi.fn()
const cb2 = vi.fn()
watch(state, cb1, { signal: controller.signal })
watch(state, cb2, { signal: controller.signal })

state.value++
await nextTick()
expect(cb1).toHaveBeenCalledTimes(1)
expect(cb2).toHaveBeenCalledTimes(1)

controller.abort()
state.value++
await nextTick()
// should not run callback
expect(cb1).toHaveBeenCalledTimes(1)
expect(cb2).toHaveBeenCalledTimes(1)
})
})
7 changes: 6 additions & 1 deletion packages/reactivity/src/watch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export type WatchCallback<V = any, OV = any> = (
export type OnCleanup = (cleanupFn: () => void) => void

export interface WatchOptions<Immediate = boolean> extends DebuggerOptions {
signal?: AbortSignal
immediate?: Immediate
deep?: boolean | number
once?: boolean
Expand Down Expand Up @@ -117,7 +118,7 @@ export class WatcherEffect extends ReactiveEffect {
public cb?: WatchCallback<any, any> | null | undefined,
public options: WatchOptions = EMPTY_OBJ,
) {
const { deep, once, call, onWarn } = options
const { deep, once, signal, call, onWarn } = options

let getter: () => any
let forceTrigger = false
Expand Down Expand Up @@ -199,6 +200,10 @@ export class WatcherEffect extends ReactiveEffect {

this.cb = cb

if (signal) {
signal.addEventListener('abort', this.stop.bind(this), { once: true })
}

this.oldValue = isMultiSource
? new Array((source as []).length).fill(INITIAL_WATCHER_VALUE)
: INITIAL_WATCHER_VALUE
Expand Down
42 changes: 42 additions & 0 deletions packages/runtime-core/__tests__/apiWatch.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,48 @@ describe('api: watch', () => {
expect(dummy).toBe(1)
})

it('stopping the watcher (effect) by abort controller', async () => {
const controller = new AbortController()
const state = reactive({ count: 0 })
let dummy
watchEffect(
() => {
dummy = state.count
},
{ signal: controller.signal },
)
expect(dummy).toBe(0)

controller.abort()
state.count++
await nextTick()
// should not update
expect(dummy).toBe(0)
})

it('stopping the watcher (with source) by abort controller', async () => {
const controller = new AbortController()
const state = reactive({ count: 0 })
let dummy
watch(
() => state.count,
count => {
dummy = count
},
{ signal: controller.signal },
)

state.count++
await nextTick()
expect(dummy).toBe(1)

controller.abort()
state.count++
await nextTick()
// should not update
expect(dummy).toBe(1)
})

it('cleanup registration (effect)', async () => {
const state = reactive({ count: 0 })
const cleanup = vi.fn()
Expand Down
10 changes: 7 additions & 3 deletions packages/runtime-core/src/apiWatch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,11 @@ type MapSources<T, Immediate> = {
: never
}

export interface WatchEffectOptions extends DebuggerOptions {
export interface BaseWatchEffectOptions extends DebuggerOptions {
signal?: AbortSignal
}

export interface WatchEffectOptions extends BaseWatchEffectOptions {
flush?: 'pre' | 'post' | 'sync'
}

Expand All @@ -63,7 +67,7 @@ export function watchEffect(

export function watchPostEffect(
effect: WatchEffect,
options?: DebuggerOptions,
options?: BaseWatchEffectOptions,
): WatchHandle {
return doWatch(
effect,
Expand All @@ -74,7 +78,7 @@ export function watchPostEffect(

export function watchSyncEffect(
effect: WatchEffect,
options?: DebuggerOptions,
options?: BaseWatchEffectOptions,
): WatchHandle {
return doWatch(
effect,
Expand Down
Loading