Skip to content

Commit ba62ccd

Browse files
authored
feat(watch): support directly watching reactive object in multiple sources with deep default (vuejs#1201)
1 parent 83b7158 commit ba62ccd

File tree

2 files changed

+46
-16
lines changed

2 files changed

+46
-16
lines changed

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,12 +155,30 @@ describe('api: watch', () => {
155155
expect(dummy).toMatchObject([[2, true], [1, false]])
156156
})
157157

158+
it('watching multiple sources: reactive object (with automatic deep: true)', async () => {
159+
const src = reactive({ count: 0 })
160+
let dummy
161+
watch([src], ([state]) => {
162+
dummy = state
163+
// assert types
164+
state.count === 1
165+
})
166+
src.count++
167+
await nextTick()
168+
expect(dummy).toMatchObject({ count: 1 })
169+
})
170+
158171
it('warn invalid watch source', () => {
159172
// @ts-ignore
160173
watch(1, () => {})
161174
expect(`Invalid watch source`).toHaveBeenWarned()
162175
})
163176

177+
it('warn invalid watch source: multiple sources', () => {
178+
watch([1], () => {})
179+
expect(`Invalid watch source`).toHaveBeenWarned()
180+
})
181+
164182
it('stopping the watcher (effect)', async () => {
165183
const state = reactive({ count: 0 })
166184
let dummy

packages/runtime-core/src/apiWatch.ts

Lines changed: 28 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -44,13 +44,17 @@ export type WatchCallback<V = any, OV = any> = (
4444
) => any
4545

4646
type MapSources<T> = {
47-
[K in keyof T]: T[K] extends WatchSource<infer V> ? V : never
47+
[K in keyof T]: T[K] extends WatchSource<infer V>
48+
? V
49+
: T[K] extends object ? T[K] : never
4850
}
4951

5052
type MapOldSources<T, Immediate> = {
5153
[K in keyof T]: T[K] extends WatchSource<infer V>
5254
? Immediate extends true ? (V | undefined) : V
53-
: never
55+
: T[K] extends object
56+
? Immediate extends true ? (T[K] | undefined) : T[K]
57+
: never
5458
}
5559

5660
type InvalidateCbRegistrator = (cb: () => void) => void
@@ -86,7 +90,7 @@ const INITIAL_WATCHER_VALUE = {}
8690
// on position in the source array. Otherwise the values will get a union type
8791
// of all possible value types.
8892
export function watch<
89-
T extends Readonly<WatchSource<unknown>[]>,
93+
T extends Readonly<Array<WatchSource<unknown> | object>>,
9094
Immediate extends Readonly<boolean> = false
9195
>(
9296
sources: T,
@@ -147,17 +151,31 @@ function doWatch(
147151
}
148152
}
149153

154+
const warnInvalidSource = (s: unknown) => {
155+
warn(
156+
`Invalid watch source: `,
157+
s,
158+
`A watch source can only be a getter/effect function, a ref, ` +
159+
`a reactive object, or an array of these types.`
160+
)
161+
}
162+
150163
const instance = currentInstance
151164

152165
let getter: () => any
153166
if (isArray(source)) {
154167
getter = () =>
155-
source.map(
156-
s =>
157-
isRef(s)
158-
? s.value
159-
: callWithErrorHandling(s, instance, ErrorCodes.WATCH_GETTER)
160-
)
168+
source.map(s => {
169+
if (isRef(s)) {
170+
return s.value
171+
} else if (isReactive(s)) {
172+
return traverse(s)
173+
} else if (isFunction(s)) {
174+
return callWithErrorHandling(s, instance, ErrorCodes.WATCH_GETTER)
175+
} else {
176+
__DEV__ && warnInvalidSource(s)
177+
}
178+
})
161179
} else if (isRef(source)) {
162180
getter = () => source.value
163181
} else if (isReactive(source)) {
@@ -187,13 +205,7 @@ function doWatch(
187205
}
188206
} else {
189207
getter = NOOP
190-
__DEV__ &&
191-
warn(
192-
`Invalid watch source: `,
193-
source,
194-
`A watch source can only be a getter/effect function, a ref, ` +
195-
`a reactive object, or an array of these types.`
196-
)
208+
__DEV__ && warnInvalidSource(source)
197209
}
198210

199211
if (cb && deep) {

0 commit comments

Comments
 (0)