Skip to content

Commit

Permalink
feat(watch): support directly watching reactive object with deep default
Browse files Browse the repository at this point in the history
Also warn invalid watch sources

close #1110
  • Loading branch information
yyx990803 committed May 4, 2020
1 parent 64ef7c7 commit 6b33cc4
Show file tree
Hide file tree
Showing 2 changed files with 70 additions and 27 deletions.
19 changes: 19 additions & 0 deletions packages/runtime-core/__tests__/apiWatch.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down
78 changes: 51 additions & 27 deletions packages/runtime-core/src/apiWatch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import {
isRef,
Ref,
ComputedRef,
ReactiveEffectOptions
ReactiveEffectOptions,
isReactive
} from '@vue/reactivity'
import { queueJob } from './scheduler'
import {
Expand Down Expand Up @@ -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.
Expand All @@ -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>[],
Expand Down Expand Up @@ -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(

This comment has been minimized.

Copy link
@jods4

jods4 May 13, 2020

Contributor

@yyx990803 should a plain object + deep be a valid use case as well?
Even if the root is not reactive, there might be refs/reactives in the graph.

`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) {
Expand Down

0 comments on commit 6b33cc4

Please sign in to comment.