feat(watch): support directly watching reactive object in multiple sources with deep default (#1201)

This commit is contained in:
Yang Mingshan 2020-05-18 23:02:51 +08:00 committed by GitHub
parent 83b7158017
commit ba62ccd55d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 46 additions and 16 deletions

View File

@ -155,12 +155,30 @@ describe('api: watch', () => {
expect(dummy).toMatchObject([[2, true], [1, false]]) expect(dummy).toMatchObject([[2, true], [1, false]])
}) })
it('watching multiple sources: reactive object (with automatic deep: true)', async () => {
const src = reactive({ count: 0 })
let dummy
watch([src], ([state]) => {
dummy = state
// assert types
state.count === 1
})
src.count++
await nextTick()
expect(dummy).toMatchObject({ count: 1 })
})
it('warn invalid watch source', () => { it('warn invalid watch source', () => {
// @ts-ignore // @ts-ignore
watch(1, () => {}) watch(1, () => {})
expect(`Invalid watch source`).toHaveBeenWarned() expect(`Invalid watch source`).toHaveBeenWarned()
}) })
it('warn invalid watch source: multiple sources', () => {
watch([1], () => {})
expect(`Invalid watch source`).toHaveBeenWarned()
})
it('stopping the watcher (effect)', async () => { it('stopping the watcher (effect)', async () => {
const state = reactive({ count: 0 }) const state = reactive({ count: 0 })
let dummy let dummy

View File

@ -44,13 +44,17 @@ export type WatchCallback<V = any, OV = any> = (
) => any ) => any
type MapSources<T> = { type MapSources<T> = {
[K in keyof T]: T[K] extends WatchSource<infer V> ? V : never [K in keyof T]: T[K] extends WatchSource<infer V>
? V
: T[K] extends object ? T[K] : never
} }
type MapOldSources<T, Immediate> = { type MapOldSources<T, Immediate> = {
[K in keyof T]: T[K] extends WatchSource<infer V> [K in keyof T]: T[K] extends WatchSource<infer V>
? Immediate extends true ? (V | undefined) : V ? Immediate extends true ? (V | undefined) : V
: never : T[K] extends object
? Immediate extends true ? (T[K] | undefined) : T[K]
: never
} }
type InvalidateCbRegistrator = (cb: () => void) => void type InvalidateCbRegistrator = (cb: () => void) => void
@ -86,7 +90,7 @@ const INITIAL_WATCHER_VALUE = {}
// on position in the source array. Otherwise the values will get a union type // on position in the source array. Otherwise the values will get a union type
// of all possible value types. // of all possible value types.
export function watch< export function watch<
T extends Readonly<WatchSource<unknown>[]>, T extends Readonly<Array<WatchSource<unknown> | object>>,
Immediate extends Readonly<boolean> = false Immediate extends Readonly<boolean> = false
>( >(
sources: T, sources: T,
@ -147,17 +151,31 @@ function doWatch(
} }
} }
const warnInvalidSource = (s: unknown) => {
warn(
`Invalid watch source: `,
s,
`A watch source can only be a getter/effect function, a ref, ` +
`a reactive object, or an array of these types.`
)
}
const instance = currentInstance const instance = currentInstance
let getter: () => any let getter: () => any
if (isArray(source)) { if (isArray(source)) {
getter = () => getter = () =>
source.map( source.map(s => {
s => if (isRef(s)) {
isRef(s) return s.value
? s.value } else if (isReactive(s)) {
: callWithErrorHandling(s, instance, ErrorCodes.WATCH_GETTER) return traverse(s)
) } else if (isFunction(s)) {
return callWithErrorHandling(s, instance, ErrorCodes.WATCH_GETTER)
} else {
__DEV__ && warnInvalidSource(s)
}
})
} else if (isRef(source)) { } else if (isRef(source)) {
getter = () => source.value getter = () => source.value
} else if (isReactive(source)) { } else if (isReactive(source)) {
@ -187,13 +205,7 @@ function doWatch(
} }
} else { } else {
getter = NOOP getter = NOOP
__DEV__ && __DEV__ && warnInvalidSource(source)
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) { if (cb && deep) {