|
|
@@ -1,7 +1,9 @@
|
|
|
+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,
|
|
|
@@ -9,17 +11,25 @@ export function initializeWatch(
|
|
|
) {
|
|
|
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
|
|
|
@@ -29,28 +39,40 @@ export function setupWatcher(
|
|
|
? () => 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)
|
|
|
@@ -62,3 +84,24 @@ export function teardownWatch(instance: MountedComponent) {
|
|
|
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
|
|
|
+}
|