diff --git a/packages/runtime-core/__tests__/apiWatch.spec.ts b/packages/runtime-core/__tests__/apiWatch.spec.ts index 8b58ded3..71dfd3ca 100644 --- a/packages/runtime-core/__tests__/apiWatch.spec.ts +++ b/packages/runtime-core/__tests__/apiWatch.spec.ts @@ -103,6 +103,19 @@ describe('api: watch', () => { expect(dummy).toMatchObject([1, 0]) }) + it('directly watching reactive object (with automatic deep: true)', async () => { + const src = reactive({ + count: 0 + }) + let dummy + watch(src, ({ count }) => { + dummy = count + }) + src.count++ + await nextTick() + expect(dummy).toBe(1) + }) + it('watching multiple sources', async () => { const state = reactive({ count: 1 }) const count = ref(1) @@ -142,6 +155,12 @@ describe('api: watch', () => { expect(dummy).toMatchObject([[2, true], [1, false]]) }) + it('warn invalid watch source', () => { + // @ts-ignore + watch(1, () => {}) + expect(`Invalid watch source`).toHaveBeenWarned() + }) + it('stopping the watcher (effect)', async () => { const state = reactive({ count: 0 }) let dummy diff --git a/packages/runtime-core/src/apiWatch.ts b/packages/runtime-core/src/apiWatch.ts index 13e5ab02..322e38e6 100644 --- a/packages/runtime-core/src/apiWatch.ts +++ b/packages/runtime-core/src/apiWatch.ts @@ -4,7 +4,8 @@ import { isRef, Ref, ComputedRef, - ReactiveEffectOptions + ReactiveEffectOptions, + isReactive } from '@vue/reactivity' import { queueJob } from './scheduler' import { @@ -80,14 +81,7 @@ export function watchEffect( // initial value for watchers to trigger on undefined initial values const INITIAL_WATCHER_VALUE = {} -// overload #1: single source + cb -export function watch = false>( - source: WatchSource, - cb: WatchCallback, - options?: WatchOptions -): WatchStopHandle - -// overload #2: array of multiple sources + cb +// overload #1: array of multiple sources + cb // Readonly constraint helps the callback to correctly infer value types based // on position in the source array. Otherwise the values will get a union type // of all possible value types. @@ -100,6 +94,23 @@ export function watch< options?: WatchOptions ): WatchStopHandle +// overload #2: single source + cb +export function watch = false>( + source: WatchSource, + cb: WatchCallback, + options?: WatchOptions +): WatchStopHandle + +// overload #3: watching reactive object w/ cb +export function watch< + T extends object, + Immediate extends Readonly = false +>( + source: T, + cb: WatchCallback, + options?: WatchOptions +): WatchStopHandle + // implementation export function watch( source: WatchSource | WatchSource[], @@ -149,26 +160,39 @@ function doWatch( ) } else if (isRef(source)) { getter = () => source.value - } else if (cb) { - // getter with cb - getter = () => - callWithErrorHandling(source, instance, ErrorCodes.WATCH_GETTER) - } else { - // no cb -> simple effect - getter = () => { - if (instance && instance.isUnmounted) { - return + } else if (isReactive(source)) { + getter = () => source + deep = true + } else if (isFunction(source)) { + if (cb) { + // getter with cb + getter = () => + callWithErrorHandling(source, instance, ErrorCodes.WATCH_GETTER) + } else { + // no cb -> simple effect + getter = () => { + if (instance && instance.isUnmounted) { + return + } + if (cleanup) { + cleanup() + } + return callWithErrorHandling( + source, + instance, + ErrorCodes.WATCH_CALLBACK, + [onInvalidate] + ) } - if (cleanup) { - cleanup() - } - return callWithErrorHandling( - source, - instance, - ErrorCodes.WATCH_CALLBACK, - [onInvalidate] - ) } + } else { + getter = NOOP + warn( + `Invalid watch source: `, + source, + `A watch source can only be a getter/effect function, a ref, ` + + `a reactive object, or an array of these types.` + ) } if (cb && deep) {