vue3-yuanma/packages/runtime-core/src/reactivity.ts

145 lines
3.4 KiB
TypeScript
Raw Normal View History

2019-05-29 23:44:59 +08:00
export {
value,
isValue,
observable,
immutable,
isObservable,
isImmutable,
unwrap,
markImmutable,
markNonReactive,
effect,
// types
ReactiveEffect,
ReactiveEffectOptions,
DebuggerEvent,
OperationTypes,
Value,
ComputedValue,
2019-05-30 21:24:40 +08:00
UnwrapValue
2019-05-29 23:44:59 +08:00
} from '@vue/observer'
import {
effect,
stop,
computed as _computed,
isValue,
Value,
ComputedValue,
ReactiveEffect,
ReactiveEffectOptions
} from '@vue/observer'
import { currentInstance } from './component'
import { queueJob, queuePostFlushCb } from './scheduler'
2019-06-06 13:04:49 +08:00
import { EMPTY_OBJ, isObject, isArray, isFunction } from '@vue/shared'
2019-05-29 23:44:59 +08:00
2019-05-30 21:31:53 +08:00
// record effects created during a component's setup() so that they can be
// stopped when the component unmounts
2019-05-29 23:44:59 +08:00
function recordEffect(effect: ReactiveEffect) {
if (currentInstance) {
;(currentInstance.effects || (currentInstance.effects = [])).push(effect)
}
}
// a wrapped version of raw computed to tear it down at component unmount
export function computed<T, C = null>(
2019-06-06 13:04:49 +08:00
getter: () => T,
setter?: (v: T) => void
2019-05-29 23:44:59 +08:00
): ComputedValue<T> {
2019-06-06 13:04:49 +08:00
const c = _computed(getter, setter)
2019-05-29 23:44:59 +08:00
recordEffect(c.effect)
return c
}
export interface WatchOptions {
lazy?: boolean
flush?: 'pre' | 'post' | 'sync'
deep?: boolean
onTrack?: ReactiveEffectOptions['onTrack']
onTrigger?: ReactiveEffectOptions['onTrigger']
}
const invoke = (fn: Function) => fn()
export function watch<T>(
source: Value<T> | (() => T),
cb?: <V extends T>(newValue: V, oldValue: V) => (() => void) | void,
options: WatchOptions = EMPTY_OBJ
): () => void {
const scheduler =
options.flush === 'sync'
? invoke
: options.flush === 'pre'
? queueJob
: queuePostFlushCb
2019-05-30 21:31:53 +08:00
const baseGetter = isValue(source) ? () => source.value : source
const getter = options.deep ? () => traverse(baseGetter()) : baseGetter
2019-05-29 23:44:59 +08:00
let oldValue: any
2019-06-06 13:04:49 +08:00
let cleanup: any
2019-05-29 23:44:59 +08:00
const applyCb = cb
? () => {
const newValue = runner()
if (options.deep || newValue !== oldValue) {
try {
2019-06-06 13:25:05 +08:00
// cleanup before running cb again
if (cleanup) {
2019-06-06 13:04:49 +08:00
cleanup()
}
2019-06-06 13:25:05 +08:00
const _cleanup = cb(newValue, oldValue)
if (isFunction(_cleanup)) {
// save cleanup so it is also called when effect is stopped
cleanup = runner.onStop = _cleanup
}
2019-05-29 23:44:59 +08:00
} catch (e) {
// TODO handle error
// handleError(e, instance, ErrorTypes.WATCH_CALLBACK)
}
oldValue = newValue
}
}
: void 0
const runner = effect(getter, {
lazy: true,
// so it runs before component update effects in pre flush mode
computed: true,
onTrack: options.onTrack,
onTrigger: options.onTrigger,
scheduler: applyCb ? () => scheduler(applyCb) : void 0
})
if (!options.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
}