Skip to content

Commit 6b33cc4

Browse files
committed
feat(watch): support directly watching reactive object with deep default
Also warn invalid watch sources close #1110
1 parent 64ef7c7 commit 6b33cc4

File tree

2 files changed

+70
-27
lines changed

2 files changed

+70
-27
lines changed

packages/runtime-core/__tests__/apiWatch.spec.ts

+19
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,19 @@ describe('api: watch', () => {
103103
expect(dummy).toMatchObject([1, 0])
104104
})
105105

106+
it('directly watching reactive object (with automatic deep: true)', async () => {
107+
const src = reactive({
108+
count: 0
109+
})
110+
let dummy
111+
watch(src, ({ count }) => {
112+
dummy = count
113+
})
114+
src.count++
115+
await nextTick()
116+
expect(dummy).toBe(1)
117+
})
118+
106119
it('watching multiple sources', async () => {
107120
const state = reactive({ count: 1 })
108121
const count = ref(1)
@@ -142,6 +155,12 @@ describe('api: watch', () => {
142155
expect(dummy).toMatchObject([[2, true], [1, false]])
143156
})
144157

158+
it('warn invalid watch source', () => {
159+
// @ts-ignore
160+
watch(1, () => {})
161+
expect(`Invalid watch source`).toHaveBeenWarned()
162+
})
163+
145164
it('stopping the watcher (effect)', async () => {
146165
const state = reactive({ count: 0 })
147166
let dummy

packages/runtime-core/src/apiWatch.ts

+51-27
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ import {
44
isRef,
55
Ref,
66
ComputedRef,
7-
ReactiveEffectOptions
7+
ReactiveEffectOptions,
8+
isReactive
89
} from '@vue/reactivity'
910
import { queueJob } from './scheduler'
1011
import {
@@ -80,14 +81,7 @@ export function watchEffect(
8081
// initial value for watchers to trigger on undefined initial values
8182
const INITIAL_WATCHER_VALUE = {}
8283

83-
// overload #1: single source + cb
84-
export function watch<T, Immediate extends Readonly<boolean> = false>(
85-
source: WatchSource<T>,
86-
cb: WatchCallback<T, Immediate extends true ? (T | undefined) : T>,
87-
options?: WatchOptions<Immediate>
88-
): WatchStopHandle
89-
90-
// overload #2: array of multiple sources + cb
84+
// overload #1: array of multiple sources + cb
9185
// Readonly constraint helps the callback to correctly infer value types based
9286
// on position in the source array. Otherwise the values will get a union type
9387
// of all possible value types.
@@ -100,6 +94,23 @@ export function watch<
10094
options?: WatchOptions<Immediate>
10195
): WatchStopHandle
10296

97+
// overload #2: single source + cb
98+
export function watch<T, Immediate extends Readonly<boolean> = false>(
99+
source: WatchSource<T>,
100+
cb: WatchCallback<T, Immediate extends true ? (T | undefined) : T>,
101+
options?: WatchOptions<Immediate>
102+
): WatchStopHandle
103+
104+
// overload #3: watching reactive object w/ cb
105+
export function watch<
106+
T extends object,
107+
Immediate extends Readonly<boolean> = false
108+
>(
109+
source: T,
110+
cb: WatchCallback<T, Immediate extends true ? (T | undefined) : T>,
111+
options?: WatchOptions<Immediate>
112+
): WatchStopHandle
113+
103114
// implementation
104115
export function watch<T = any>(
105116
source: WatchSource<T> | WatchSource<T>[],
@@ -149,26 +160,39 @@ function doWatch(
149160
)
150161
} else if (isRef(source)) {
151162
getter = () => source.value
152-
} else if (cb) {
153-
// getter with cb
154-
getter = () =>
155-
callWithErrorHandling(source, instance, ErrorCodes.WATCH_GETTER)
156-
} else {
157-
// no cb -> simple effect
158-
getter = () => {
159-
if (instance && instance.isUnmounted) {
160-
return
161-
}
162-
if (cleanup) {
163-
cleanup()
163+
} else if (isReactive(source)) {
164+
getter = () => source
165+
deep = true
166+
} else if (isFunction(source)) {
167+
if (cb) {
168+
// getter with cb
169+
getter = () =>
170+
callWithErrorHandling(source, instance, ErrorCodes.WATCH_GETTER)
171+
} else {
172+
// no cb -> simple effect
173+
getter = () => {
174+
if (instance && instance.isUnmounted) {
175+
return
176+
}
177+
if (cleanup) {
178+
cleanup()
179+
}
180+
return callWithErrorHandling(
181+
source,
182+
instance,
183+
ErrorCodes.WATCH_CALLBACK,
184+
[onInvalidate]
185+
)
164186
}
165-
return callWithErrorHandling(
166-
source,
167-
instance,
168-
ErrorCodes.WATCH_CALLBACK,
169-
[onInvalidate]
170-
)
171187
}
188+
} else {
189+
getter = NOOP
190+
warn(
191+
`Invalid watch source: `,
192+
source,
193+
`A watch source can only be a getter/effect function, a ref, ` +
194+
`a reactive object, or an array of these types.`
195+
)
172196
}
173197

174198
if (cb && deep) {

0 commit comments

Comments
 (0)