+import { EMPTY_OBJ } from './utils'
import { MountedComponent } from './component'
-import { ComponentWatchOptions } from './componentOptions'
+import { ComponentWatchOptions, WatchOptions } from './componentOptions'
import { autorun, stop } from '@vue/observer'
import { queueJob } from '@vue/scheduler'
+import { handleError, ErrorTypes } from './errorHandling'
export function initializeWatch(
instance: MountedComponent,
) {
if (options !== void 0) {
for (const key in options) {
- setupWatcher(instance, key, options[key])
+ const opt = options[key]
+ if (Array.isArray(opt)) {
+ opt.forEach(o => setupWatcher(instance, key, o))
+ } else if (typeof opt === 'function') {
+ setupWatcher(instance, key, opt)
+ } else if (typeof opt === 'string') {
+ setupWatcher(instance, key, (instance as any)[opt])
+ } else if (opt.handler) {
+ setupWatcher(instance, key, opt.handler, opt)
+ }
}
}
}
-// TODO deep watch
-// TODO sync watch
export function setupWatcher(
instance: MountedComponent,
keyOrFn: string | Function,
- cb: Function
+ cb: (newValue: any, oldValue: any) => void,
+ options: WatchOptions = EMPTY_OBJ as WatchOptions
): () => void {
const handles = instance._watchHandles || (instance._watchHandles = new Set())
const proxy = instance.$proxy
? () => proxy[keyOrFn]
: () => keyOrFn.call(proxy)
+ const getter = options.deep ? () => traverse(rawGetter()) : rawGetter
+
let oldValue: any
const applyCb = () => {
const newValue = runner()
- if (newValue !== oldValue) {
- // TODO handle error
- cb(newValue, oldValue)
+ if (options.deep || newValue !== oldValue) {
oldValue = newValue
+ try {
+ cb.call(instance.$proxy, newValue, oldValue)
+ } catch (e) {
+ handleError(e, instance, ErrorTypes.WATCH_CALLBACK)
+ }
}
}
- const runner = autorun(rawGetter, {
- scheduler: () => {
- // defer watch callback using the scheduler so that multiple mutations
- // result in one call only.
- queueJob(applyCb)
- }
+ const runner = autorun(getter, {
+ lazy: true,
+ scheduler: options.sync
+ ? applyCb
+ : () => {
+ // defer watch callback using the scheduler so that multiple mutations
+ // result in one call only.
+ queueJob(applyCb)
+ }
})
oldValue = runner()
handles.add(runner)
+ if (options.immediate) {
+ cb.call(instance.$proxy, oldValue, undefined)
+ }
+
return () => {
stop(runner)
handles.delete(runner)
instance._watchHandles.forEach(stop)
}
}
+
+function traverse(value: any, seen: Set<any> = new Set()) {
+ if (value === null || typeof value !== 'object' || seen.has(value)) {
+ return
+ }
+ seen.add(value)
+ if (Array.isArray(value)) {
+ for (let i = 0; i < value.length; i++) {
+ traverse(value[i], seen)
+ }
+ } else if (value instanceof Map || value instanceof Set) {
+ ;(value as any).forEach((v: any) => {
+ traverse(v, seen)
+ })
+ } else {
+ for (const key in value) {
+ traverse(value[key], seen)
+ }
+ }
+ return value
+}