Explorar el Código

feat(watch): support directly watching reactive object with deep default

Also warn invalid watch sources

close #1110
Evan You hace 6 años
padre
commit
6b33cc4229

+ 19 - 0
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

+ 51 - 27
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<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.
@@ -100,6 +94,23 @@ export function watch<
   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>[],
@@ -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
-      }
-      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) {