2018-11-14 00:03:35 +08:00
|
|
|
import { OperationTypes } from './operations'
|
2019-08-16 21:42:46 +08:00
|
|
|
import { Dep, targetMap } from './reactive'
|
2019-08-23 10:07:51 +08:00
|
|
|
import { EMPTY_OBJ, extend } from '@vue/shared'
|
2018-11-14 00:03:35 +08:00
|
|
|
|
2019-10-09 01:48:13 +08:00
|
|
|
export interface ReactiveEffect<T = any> {
|
|
|
|
(): T
|
2019-10-22 23:26:48 +08:00
|
|
|
_isEffect: true
|
2018-11-14 00:03:35 +08:00
|
|
|
active: boolean
|
2019-10-09 01:48:13 +08:00
|
|
|
raw: () => T
|
2018-11-14 00:03:35 +08:00
|
|
|
deps: Array<Dep>
|
|
|
|
computed?: boolean
|
2019-09-07 00:58:31 +08:00
|
|
|
scheduler?: (run: Function) => void
|
|
|
|
onTrack?: (event: DebuggerEvent) => void
|
|
|
|
onTrigger?: (event: DebuggerEvent) => void
|
2019-06-06 13:25:05 +08:00
|
|
|
onStop?: () => void
|
2018-11-14 00:03:35 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
export interface ReactiveEffectOptions {
|
|
|
|
lazy?: boolean
|
2019-05-29 23:44:59 +08:00
|
|
|
computed?: boolean
|
2019-09-07 00:58:31 +08:00
|
|
|
scheduler?: (run: Function) => void
|
|
|
|
onTrack?: (event: DebuggerEvent) => void
|
|
|
|
onTrigger?: (event: DebuggerEvent) => void
|
2019-06-06 13:25:05 +08:00
|
|
|
onStop?: () => void
|
2018-11-14 00:03:35 +08:00
|
|
|
}
|
|
|
|
|
2019-10-22 23:26:48 +08:00
|
|
|
type DebuggerEvent = {
|
2018-11-14 00:03:35 +08:00
|
|
|
effect: ReactiveEffect
|
2019-10-22 23:26:48 +08:00
|
|
|
target: object
|
2018-11-14 00:03:35 +08:00
|
|
|
type: OperationTypes
|
2019-10-22 23:26:48 +08:00
|
|
|
key: any
|
|
|
|
} & DebuggerEventExtraInfo
|
|
|
|
|
|
|
|
export interface DebuggerEventExtraInfo {
|
|
|
|
newValue?: any
|
|
|
|
oldValue?: any
|
|
|
|
oldTarget?: Map<any, any> | Set<any>
|
2018-11-14 00:03:35 +08:00
|
|
|
}
|
|
|
|
|
2019-10-17 11:12:57 +08:00
|
|
|
export const effectStack: ReactiveEffect[] = []
|
2018-11-14 00:03:35 +08:00
|
|
|
|
|
|
|
export const ITERATE_KEY = Symbol('iterate')
|
|
|
|
|
2019-10-10 00:16:29 +08:00
|
|
|
export function isEffect(fn: any): fn is ReactiveEffect {
|
2019-10-22 23:26:48 +08:00
|
|
|
return fn != null && fn._isEffect === true
|
2019-10-10 00:16:29 +08:00
|
|
|
}
|
|
|
|
|
2019-10-09 01:48:13 +08:00
|
|
|
export function effect<T = any>(
|
|
|
|
fn: () => T,
|
2019-06-12 00:03:50 +08:00
|
|
|
options: ReactiveEffectOptions = EMPTY_OBJ
|
2019-10-09 01:48:13 +08:00
|
|
|
): ReactiveEffect<T> {
|
2019-10-10 00:16:29 +08:00
|
|
|
if (isEffect(fn)) {
|
|
|
|
fn = fn.raw
|
2019-06-12 00:03:50 +08:00
|
|
|
}
|
|
|
|
const effect = createReactiveEffect(fn, options)
|
|
|
|
if (!options.lazy) {
|
|
|
|
effect()
|
|
|
|
}
|
|
|
|
return effect
|
|
|
|
}
|
|
|
|
|
|
|
|
export function stop(effect: ReactiveEffect) {
|
|
|
|
if (effect.active) {
|
|
|
|
cleanup(effect)
|
|
|
|
if (effect.onStop) {
|
|
|
|
effect.onStop()
|
|
|
|
}
|
|
|
|
effect.active = false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-10-09 01:48:13 +08:00
|
|
|
function createReactiveEffect<T = any>(
|
|
|
|
fn: () => T,
|
2018-11-14 00:03:35 +08:00
|
|
|
options: ReactiveEffectOptions
|
2019-10-09 01:48:13 +08:00
|
|
|
): ReactiveEffect<T> {
|
2019-10-22 23:26:48 +08:00
|
|
|
const effect = function reactiveEffect(...args: unknown[]): unknown {
|
2019-10-10 00:16:29 +08:00
|
|
|
return run(effect, fn, args)
|
|
|
|
} as ReactiveEffect
|
2019-10-22 23:26:48 +08:00
|
|
|
effect._isEffect = true
|
2018-11-14 00:03:35 +08:00
|
|
|
effect.active = true
|
|
|
|
effect.raw = fn
|
|
|
|
effect.scheduler = options.scheduler
|
|
|
|
effect.onTrack = options.onTrack
|
|
|
|
effect.onTrigger = options.onTrigger
|
2019-06-06 13:25:05 +08:00
|
|
|
effect.onStop = options.onStop
|
2019-05-29 23:44:59 +08:00
|
|
|
effect.computed = options.computed
|
2018-11-14 00:03:35 +08:00
|
|
|
effect.deps = []
|
|
|
|
return effect
|
|
|
|
}
|
|
|
|
|
2019-10-22 23:26:48 +08:00
|
|
|
function run(effect: ReactiveEffect, fn: Function, args: unknown[]): unknown {
|
2018-11-14 00:03:35 +08:00
|
|
|
if (!effect.active) {
|
|
|
|
return fn(...args)
|
|
|
|
}
|
2019-10-17 11:12:57 +08:00
|
|
|
if (!effectStack.includes(effect)) {
|
2018-11-14 00:03:35 +08:00
|
|
|
cleanup(effect)
|
|
|
|
try {
|
2019-10-17 11:12:57 +08:00
|
|
|
effectStack.push(effect)
|
2018-11-14 00:03:35 +08:00
|
|
|
return fn(...args)
|
|
|
|
} finally {
|
2019-10-17 11:12:57 +08:00
|
|
|
effectStack.pop()
|
2018-11-14 00:03:35 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-06-12 00:03:50 +08:00
|
|
|
function cleanup(effect: ReactiveEffect) {
|
2019-06-02 16:35:19 +08:00
|
|
|
const { deps } = effect
|
|
|
|
if (deps.length) {
|
|
|
|
for (let i = 0; i < deps.length; i++) {
|
|
|
|
deps[i].delete(effect)
|
|
|
|
}
|
|
|
|
deps.length = 0
|
2018-11-14 00:03:35 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-05 06:20:47 +08:00
|
|
|
let shouldTrack = true
|
|
|
|
|
|
|
|
export function pauseTracking() {
|
|
|
|
shouldTrack = false
|
|
|
|
}
|
|
|
|
|
|
|
|
export function resumeTracking() {
|
|
|
|
shouldTrack = true
|
|
|
|
}
|
|
|
|
|
2019-10-22 23:26:48 +08:00
|
|
|
export function track(target: object, type: OperationTypes, key?: unknown) {
|
2019-10-17 11:12:57 +08:00
|
|
|
if (!shouldTrack || effectStack.length === 0) {
|
2019-10-16 13:58:11 +08:00
|
|
|
return
|
|
|
|
}
|
2019-10-17 11:12:57 +08:00
|
|
|
const effect = effectStack[effectStack.length - 1]
|
2019-10-16 13:58:11 +08:00
|
|
|
if (type === OperationTypes.ITERATE) {
|
|
|
|
key = ITERATE_KEY
|
|
|
|
}
|
|
|
|
let depsMap = targetMap.get(target)
|
|
|
|
if (depsMap === void 0) {
|
|
|
|
targetMap.set(target, (depsMap = new Map()))
|
|
|
|
}
|
|
|
|
let dep = depsMap.get(key!)
|
|
|
|
if (dep === void 0) {
|
|
|
|
depsMap.set(key!, (dep = new Set()))
|
|
|
|
}
|
|
|
|
if (!dep.has(effect)) {
|
|
|
|
dep.add(effect)
|
|
|
|
effect.deps.push(dep)
|
|
|
|
if (__DEV__ && effect.onTrack) {
|
|
|
|
effect.onTrack({
|
|
|
|
effect,
|
|
|
|
target,
|
|
|
|
type,
|
|
|
|
key
|
|
|
|
})
|
2018-11-14 00:03:35 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export function trigger(
|
2019-10-22 23:26:48 +08:00
|
|
|
target: object,
|
2018-11-14 00:03:35 +08:00
|
|
|
type: OperationTypes,
|
2019-10-22 23:26:48 +08:00
|
|
|
key?: unknown,
|
|
|
|
extraInfo?: DebuggerEventExtraInfo
|
2018-11-14 00:03:35 +08:00
|
|
|
) {
|
2019-05-29 17:36:53 +08:00
|
|
|
const depsMap = targetMap.get(target)
|
|
|
|
if (depsMap === void 0) {
|
|
|
|
// never been tracked
|
|
|
|
return
|
|
|
|
}
|
2019-10-06 23:22:32 +08:00
|
|
|
const effects = new Set<ReactiveEffect>()
|
|
|
|
const computedRunners = new Set<ReactiveEffect>()
|
2018-11-14 00:03:35 +08:00
|
|
|
if (type === OperationTypes.CLEAR) {
|
|
|
|
// collection being cleared, trigger all effects for target
|
|
|
|
depsMap.forEach(dep => {
|
|
|
|
addRunners(effects, computedRunners, dep)
|
|
|
|
})
|
|
|
|
} else {
|
|
|
|
// schedule runs for SET | ADD | DELETE
|
|
|
|
if (key !== void 0) {
|
2019-10-05 22:38:02 +08:00
|
|
|
addRunners(effects, computedRunners, depsMap.get(key))
|
2018-11-14 00:03:35 +08:00
|
|
|
}
|
|
|
|
// also run for iteration key on ADD | DELETE
|
|
|
|
if (type === OperationTypes.ADD || type === OperationTypes.DELETE) {
|
|
|
|
const iterationKey = Array.isArray(target) ? 'length' : ITERATE_KEY
|
|
|
|
addRunners(effects, computedRunners, depsMap.get(iterationKey))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
const run = (effect: ReactiveEffect) => {
|
|
|
|
scheduleRun(effect, target, type, key, extraInfo)
|
|
|
|
}
|
|
|
|
// Important: computed effects must be run first so that computed getters
|
|
|
|
// can be invalidated before any normal effects that depend on them are run.
|
|
|
|
computedRunners.forEach(run)
|
|
|
|
effects.forEach(run)
|
|
|
|
}
|
|
|
|
|
|
|
|
function addRunners(
|
|
|
|
effects: Set<ReactiveEffect>,
|
|
|
|
computedRunners: Set<ReactiveEffect>,
|
|
|
|
effectsToAdd: Set<ReactiveEffect> | undefined
|
|
|
|
) {
|
|
|
|
if (effectsToAdd !== void 0) {
|
|
|
|
effectsToAdd.forEach(effect => {
|
|
|
|
if (effect.computed) {
|
|
|
|
computedRunners.add(effect)
|
|
|
|
} else {
|
|
|
|
effects.add(effect)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function scheduleRun(
|
|
|
|
effect: ReactiveEffect,
|
2019-10-22 23:26:48 +08:00
|
|
|
target: object,
|
2018-11-14 00:03:35 +08:00
|
|
|
type: OperationTypes,
|
2019-10-22 23:26:48 +08:00
|
|
|
key: unknown,
|
|
|
|
extraInfo?: DebuggerEventExtraInfo
|
2018-11-14 00:03:35 +08:00
|
|
|
) {
|
|
|
|
if (__DEV__ && effect.onTrigger) {
|
2019-10-22 23:26:48 +08:00
|
|
|
const event: DebuggerEvent = {
|
|
|
|
effect,
|
|
|
|
target,
|
|
|
|
key,
|
|
|
|
type
|
|
|
|
}
|
|
|
|
effect.onTrigger(extraInfo ? extend(event, extraInfo) : event)
|
2018-11-14 00:03:35 +08:00
|
|
|
}
|
|
|
|
if (effect.scheduler !== void 0) {
|
|
|
|
effect.scheduler(effect)
|
|
|
|
} else {
|
|
|
|
effect()
|
|
|
|
}
|
|
|
|
}
|