wip: restructure api + support watch multiple sources
This commit is contained in:
107
packages/runtime-core/src/apiWatch.ts
Normal file
107
packages/runtime-core/src/apiWatch.ts
Normal file
@@ -0,0 +1,107 @@
|
||||
import {
|
||||
effect,
|
||||
stop,
|
||||
isValue,
|
||||
Value,
|
||||
ReactiveEffectOptions
|
||||
} from '@vue/observer'
|
||||
import { queueJob, queuePostFlushCb } from './scheduler'
|
||||
import { EMPTY_OBJ, isObject, isArray } from '@vue/shared'
|
||||
import { recordEffect } from './apiState'
|
||||
|
||||
export interface WatchOptions {
|
||||
lazy?: boolean
|
||||
flush?: 'pre' | 'post' | 'sync'
|
||||
deep?: boolean
|
||||
onTrack?: ReactiveEffectOptions['onTrack']
|
||||
onTrigger?: ReactiveEffectOptions['onTrigger']
|
||||
}
|
||||
|
||||
type WatcherSource<T> = Value<T> | (() => T)
|
||||
|
||||
const invoke = (fn: Function) => fn()
|
||||
|
||||
export function watch<T>(
|
||||
source: WatcherSource<T> | WatcherSource<T>[],
|
||||
cb?: <V extends T>(
|
||||
newValue: V,
|
||||
oldValue: V,
|
||||
onInvalidate: (fn: () => void) => void
|
||||
) => any | void,
|
||||
{ lazy, flush, deep, onTrack, onTrigger }: WatchOptions = EMPTY_OBJ
|
||||
): () => void {
|
||||
const scheduler =
|
||||
flush === 'sync' ? invoke : flush === 'pre' ? queueJob : queuePostFlushCb
|
||||
|
||||
const baseGetter = isArray(source)
|
||||
? () => source.map(s => (isValue(s) ? s.value : s()))
|
||||
: isValue(source)
|
||||
? () => source.value
|
||||
: source
|
||||
const getter = deep ? () => traverse(baseGetter()) : baseGetter
|
||||
|
||||
let cleanup: any
|
||||
const registerCleanup = (fn: () => void) => {
|
||||
// TODO wrap the cleanup fn for error handling
|
||||
cleanup = runner.onStop = fn
|
||||
}
|
||||
|
||||
let oldValue: any
|
||||
const applyCb = cb
|
||||
? () => {
|
||||
const newValue = runner()
|
||||
if (deep || newValue !== oldValue) {
|
||||
// cleanup before running cb again
|
||||
if (cleanup) {
|
||||
cleanup()
|
||||
}
|
||||
// TODO handle error (including ASYNC)
|
||||
try {
|
||||
cb(newValue, oldValue, registerCleanup)
|
||||
} catch (e) {}
|
||||
oldValue = newValue
|
||||
}
|
||||
}
|
||||
: void 0
|
||||
|
||||
const runner = effect(getter, {
|
||||
lazy: true,
|
||||
// so it runs before component update effects in pre flush mode
|
||||
computed: true,
|
||||
onTrack,
|
||||
onTrigger,
|
||||
scheduler: applyCb ? () => scheduler(applyCb) : void 0
|
||||
})
|
||||
|
||||
if (!lazy) {
|
||||
applyCb && scheduler(applyCb)
|
||||
} else {
|
||||
oldValue = runner()
|
||||
}
|
||||
|
||||
recordEffect(runner)
|
||||
return () => {
|
||||
stop(runner)
|
||||
}
|
||||
}
|
||||
|
||||
function traverse(value: any, seen: Set<any> = new Set()) {
|
||||
if (!isObject(value) || seen.has(value)) {
|
||||
return
|
||||
}
|
||||
seen.add(value)
|
||||
if (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
|
||||
}
|
||||
Reference in New Issue
Block a user