2018-10-16 15:47:51 -04:00
|
|
|
import {
|
|
|
|
EMPTY_OBJ,
|
|
|
|
NOOP,
|
|
|
|
isFunction,
|
|
|
|
isArray,
|
|
|
|
isString,
|
|
|
|
isObject
|
|
|
|
} from '@vue/shared'
|
2018-10-09 11:37:24 -04:00
|
|
|
import { ComponentInstance } from './component'
|
2018-09-23 23:16:14 -04:00
|
|
|
import { ComponentWatchOptions, WatchOptions } from './componentOptions'
|
2018-09-20 18:57:13 -04:00
|
|
|
import { autorun, stop } from '@vue/observer'
|
2018-09-21 13:34:00 -04:00
|
|
|
import { queueJob } from '@vue/scheduler'
|
2018-09-23 23:16:14 -04:00
|
|
|
import { handleError, ErrorTypes } from './errorHandling'
|
2018-10-11 17:21:13 -04:00
|
|
|
import { warn } from './warning'
|
2018-09-19 11:35:38 -04:00
|
|
|
|
|
|
|
export function initializeWatch(
|
2018-10-09 11:37:24 -04:00
|
|
|
instance: ComponentInstance,
|
2018-09-19 11:35:38 -04:00
|
|
|
options: ComponentWatchOptions | undefined
|
|
|
|
) {
|
|
|
|
if (options !== void 0) {
|
|
|
|
for (const key in options) {
|
2018-09-23 23:16:14 -04:00
|
|
|
const opt = options[key]
|
2018-10-16 15:47:51 -04:00
|
|
|
if (isArray(opt)) {
|
2018-09-23 23:16:14 -04:00
|
|
|
opt.forEach(o => setupWatcher(instance, key, o))
|
2018-10-16 15:47:51 -04:00
|
|
|
} else if (isFunction(opt)) {
|
2018-09-23 23:16:14 -04:00
|
|
|
setupWatcher(instance, key, opt)
|
2018-10-16 15:47:51 -04:00
|
|
|
} else if (isString(opt)) {
|
2018-09-23 23:16:14 -04:00
|
|
|
setupWatcher(instance, key, (instance as any)[opt])
|
|
|
|
} else if (opt.handler) {
|
|
|
|
setupWatcher(instance, key, opt.handler, opt)
|
|
|
|
}
|
2018-09-19 11:35:38 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export function setupWatcher(
|
2018-10-09 11:37:24 -04:00
|
|
|
instance: ComponentInstance,
|
2018-09-19 11:35:38 -04:00
|
|
|
keyOrFn: string | Function,
|
2018-09-23 23:16:14 -04:00
|
|
|
cb: (newValue: any, oldValue: any) => void,
|
|
|
|
options: WatchOptions = EMPTY_OBJ as WatchOptions
|
2018-09-19 11:35:38 -04:00
|
|
|
): () => void {
|
|
|
|
const handles = instance._watchHandles || (instance._watchHandles = new Set())
|
|
|
|
const proxy = instance.$proxy
|
2018-09-20 18:57:13 -04:00
|
|
|
|
2018-10-16 15:47:51 -04:00
|
|
|
const rawGetter = isString(keyOrFn)
|
|
|
|
? parseDotPath(keyOrFn, proxy)
|
|
|
|
: () => keyOrFn.call(proxy)
|
2018-09-20 18:57:13 -04:00
|
|
|
|
2018-09-24 21:52:27 -04:00
|
|
|
if (__DEV__ && rawGetter === NOOP) {
|
2018-10-11 17:21:13 -04:00
|
|
|
warn(
|
2018-09-24 21:52:27 -04:00
|
|
|
`Failed watching expression: "${keyOrFn}". ` +
|
|
|
|
`Watch expressions can only be dot-delimited paths. ` +
|
|
|
|
`For more complex expressions, use $watch with a function instead.`
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2018-09-23 23:16:14 -04:00
|
|
|
const getter = options.deep ? () => traverse(rawGetter()) : rawGetter
|
|
|
|
|
2018-09-19 11:35:38 -04:00
|
|
|
let oldValue: any
|
2018-09-20 18:57:13 -04:00
|
|
|
|
|
|
|
const applyCb = () => {
|
|
|
|
const newValue = runner()
|
2018-09-23 23:16:14 -04:00
|
|
|
if (options.deep || newValue !== oldValue) {
|
2018-09-20 18:57:13 -04:00
|
|
|
oldValue = newValue
|
2018-09-23 23:16:14 -04:00
|
|
|
try {
|
|
|
|
cb.call(instance.$proxy, newValue, oldValue)
|
|
|
|
} catch (e) {
|
|
|
|
handleError(e, instance, ErrorTypes.WATCH_CALLBACK)
|
|
|
|
}
|
2018-09-20 18:57:13 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-09-23 23:16:14 -04:00
|
|
|
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)
|
|
|
|
}
|
2018-09-19 11:35:38 -04:00
|
|
|
})
|
2018-09-20 18:57:13 -04:00
|
|
|
|
2018-09-19 11:35:38 -04:00
|
|
|
oldValue = runner()
|
|
|
|
handles.add(runner)
|
2018-09-20 18:57:13 -04:00
|
|
|
|
2018-09-23 23:16:14 -04:00
|
|
|
if (options.immediate) {
|
|
|
|
cb.call(instance.$proxy, oldValue, undefined)
|
|
|
|
}
|
|
|
|
|
2018-09-19 11:35:38 -04:00
|
|
|
return () => {
|
|
|
|
stop(runner)
|
|
|
|
handles.delete(runner)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-10-09 11:37:24 -04:00
|
|
|
export function teardownWatch(instance: ComponentInstance) {
|
2018-09-19 11:35:38 -04:00
|
|
|
if (instance._watchHandles !== null) {
|
|
|
|
instance._watchHandles.forEach(stop)
|
|
|
|
}
|
|
|
|
}
|
2018-09-23 23:16:14 -04:00
|
|
|
|
2018-09-24 21:52:27 -04:00
|
|
|
const bailRE = /[^\w.$]/
|
|
|
|
|
|
|
|
function parseDotPath(path: string, ctx: any): Function {
|
|
|
|
if (bailRE.test(path)) {
|
|
|
|
return NOOP
|
|
|
|
}
|
|
|
|
const segments = path.split('.')
|
|
|
|
if (segments.length === 1) {
|
|
|
|
return () => ctx[path]
|
|
|
|
} else {
|
|
|
|
return () => {
|
|
|
|
let obj = ctx
|
|
|
|
for (let i = 0; i < segments.length; i++) {
|
|
|
|
if (!obj) return
|
|
|
|
obj = obj[segments[i]]
|
|
|
|
}
|
|
|
|
return obj
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-09-23 23:16:14 -04:00
|
|
|
function traverse(value: any, seen: Set<any> = new Set()) {
|
2018-10-16 15:47:51 -04:00
|
|
|
if (!isObject(value) || seen.has(value)) {
|
2018-09-23 23:16:14 -04:00
|
|
|
return
|
|
|
|
}
|
|
|
|
seen.add(value)
|
2018-10-16 15:47:51 -04:00
|
|
|
if (isArray(value)) {
|
2018-09-23 23:16:14 -04:00
|
|
|
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
|
|
|
|
}
|