]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
feat: full watch api
authorEvan You <yyx990803@gmail.com>
Mon, 24 Sep 2018 03:16:14 +0000 (23:16 -0400)
committerEvan You <yyx990803@gmail.com>
Mon, 24 Sep 2018 03:16:14 +0000 (23:16 -0400)
packages/core/src/component.ts
packages/core/src/componentOptions.ts
packages/core/src/componentWatch.ts
packages/observer/src/index.ts

index a40a3488d6115c53cb5d4c7231a24ad5822a099d..1790aa330d3e667bbbd04f9301f966f06902ee0f 100644 (file)
@@ -4,7 +4,8 @@ import {
   Data,
   RenderFunction,
   ComponentOptions,
-  ComponentPropsOptions
+  ComponentPropsOptions,
+  WatchOptions
 } from './componentOptions'
 import { setupWatcher } from './componentWatch'
 import { Autorun, DebuggerEvent, ComputedGetter } from '@vue/observer'
@@ -102,9 +103,10 @@ export class Component {
   $watch(
     this: MountedComponent,
     keyOrFn: string | (() => any),
-    cb: () => void
+    cb: () => void,
+    options?: WatchOptions
   ) {
-    return setupWatcher(this, keyOrFn, cb)
+    return setupWatcher(this, keyOrFn, cb, options)
   }
 
   // eventEmitter interface
index 2bcbf1a3503a7ef00ad6b4a2a634e9f30877d6bf..d459a82e70a9bdef1fb23aa2f9aa6362091ceb52 100644 (file)
@@ -43,9 +43,23 @@ export interface ComponentComputedOptions<D = Data, P = Data> {
 }
 
 export interface ComponentWatchOptions<D = Data, P = Data> {
-  [key: string]: (
-    this: MountedComponent<D, P> & D & P,
-    oldValue: any,
-    newValue: any
-  ) => void
+  [key: string]: ComponentWatchOption<MountedComponent<D, P> & D & P>
+}
+
+export type ComponentWatchOption<C = any> =
+  | WatchHandler<C>
+  | WatchHandler<C>[]
+  | WatchOptionsWithHandler<C>
+  | string
+
+export type WatchHandler<C = any> = (this: C, val: any, oldVal: any) => void
+
+export interface WatchOptionsWithHandler<C = any> extends WatchOptions {
+  handler: WatchHandler<C>
+}
+
+export interface WatchOptions {
+  sync?: boolean
+  deep?: boolean
+  immediate?: boolean
 }
index a847ed3fd8e9ce55408cb51a3221dcb3c47e2874..17a9eb12b1248fa7be800255e4c43cc125b8efc3 100644 (file)
@@ -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
+}
index ca181a56a4833b80b58c195fb1d8b7f994c3c073..7f8a9f5ab096e14c4d7597d71b79dc3dcfa0f839 100644 (file)
@@ -83,8 +83,11 @@ function createObservable(
   baseHandlers: ProxyHandler<any>,
   collectionHandlers: ProxyHandler<any>
 ) {
-  if ((__DEV__ && target === null) || typeof target !== 'object') {
-    throw new Error(`value is not observable: ${String(target)}`)
+  if (target === null || typeof target !== 'object') {
+    if (__DEV__) {
+      console.warn(`value is not observable: ${String(target)}`)
+    }
+    return target
   }
   // target already has corresponding Proxy
   let observed = toProxy.get(target)