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)
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
isRef,
Ref,
ComputedRef,
- ReactiveEffectOptions
+ ReactiveEffectOptions,
+ isReactive
} from '@vue/reactivity'
import { queueJob } from './scheduler'
import {
// initial value for watchers to trigger on undefined initial values
const INITIAL_WATCHER_VALUE = {}
-// overload #1: single source + cb
-export function watch<T, Immediate extends Readonly<boolean> = false>(
- source: WatchSource<T>,
- cb: WatchCallback<T, Immediate extends true ? (T | undefined) : T>,
- options?: WatchOptions<Immediate>
-): 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.
options?: WatchOptions<Immediate>
): WatchStopHandle
+// overload #2: single source + cb
+export function watch<T, Immediate extends Readonly<boolean> = false>(
+ source: WatchSource<T>,
+ cb: WatchCallback<T, Immediate extends true ? (T | undefined) : T>,
+ options?: WatchOptions<Immediate>
+): WatchStopHandle
+
+// overload #3: watching reactive object w/ cb
+export function watch<
+ T extends object,
+ Immediate extends Readonly<boolean> = false
+>(
+ source: T,
+ cb: WatchCallback<T, Immediate extends true ? (T | undefined) : T>,
+ options?: WatchOptions<Immediate>
+): WatchStopHandle
+
// implementation
export function watch<T = any>(
source: WatchSource<T> | WatchSource<T>[],
)
} 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
- }
- if (cleanup) {
- cleanup()
+ } 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]
+ )
}
- 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) {