2018-09-20 18:03:59 -04:00

181 lines
4.2 KiB
TypeScript

import { OperationTypes } from './operations'
import { Dep, KeyToDepMap, targetMap } from './state'
export interface Autorun {
(): any
isAutorun: true
active: boolean
raw: Function
deps: Array<Dep>
computed?: boolean
scheduler?: Scheduler
onTrack?: Debugger
onTrigger?: Debugger
}
export interface AutorunOptions {
lazy?: boolean
scheduler?: Scheduler
onTrack?: Debugger
onTrigger?: Debugger
}
export type Scheduler = (run: () => any) => void
export type DebuggerEvent = {
runner: Autorun
target: any
type: OperationTypes
key: string | symbol | undefined
}
export type Debugger = (event: DebuggerEvent) => void
export const activeAutorunStack: Autorun[] = []
export const ITERATE_KEY = Symbol('iterate')
export function createAutorun(fn: Function, options: AutorunOptions): Autorun {
const runner = function runner(...args): any {
return run(runner as Autorun, fn, args)
} as Autorun
runner.isAutorun = true
runner.active = true
runner.raw = fn
runner.scheduler = options.scheduler
runner.onTrack = options.onTrack
runner.onTrigger = options.onTrigger
runner.deps = []
return runner
}
function run(runner: Autorun, fn: Function, args: any[]): any {
if (!runner.active) {
return fn(...args)
}
if (activeAutorunStack.indexOf(runner) === -1) {
cleanup(runner)
try {
activeAutorunStack.push(runner)
return fn(...args)
} finally {
activeAutorunStack.pop()
}
}
}
export function cleanup(runner: Autorun) {
for (let i = 0; i < runner.deps.length; i++) {
runner.deps[i].delete(runner)
}
runner.deps = []
}
export function track(
target: any,
type: OperationTypes,
key?: string | symbol
) {
const runner = activeAutorunStack[activeAutorunStack.length - 1]
if (runner) {
if (type === OperationTypes.ITERATE) {
key = ITERATE_KEY
}
// keyMap must exist because only an observed target can call this function
const depsMap = targetMap.get(target) as KeyToDepMap
let dep = depsMap.get(key as string | symbol)
if (!dep) {
depsMap.set(key as string | symbol, (dep = new Set()))
}
if (!dep.has(runner)) {
dep.add(runner)
runner.deps.push(dep)
if (__DEV__ && runner.onTrack) {
runner.onTrack({
runner,
target,
type,
key
})
}
}
}
}
export function trigger(
target: any,
type: OperationTypes,
key?: string | symbol,
extraInfo?: any
) {
const depsMap = targetMap.get(target) as KeyToDepMap
const runners = new Set()
const computedRunners = new Set()
if (type === OperationTypes.CLEAR) {
// collection being cleared, trigger all runners for target
depsMap.forEach(dep => {
addRunners(runners, computedRunners, dep)
})
} else {
// schedule runs for SET | ADD | DELETE
if (key !== void 0) {
addRunners(runners, computedRunners, depsMap.get(key as string | symbol))
}
// also run for iteration key on ADD | DELETE
if (type === OperationTypes.ADD || type === OperationTypes.DELETE) {
const iterationKey = Array.isArray(target) ? 'length' : ITERATE_KEY
addRunners(runners, computedRunners, depsMap.get(iterationKey))
}
}
const run = (runner: Autorun) => {
scheduleRun(runner, target, type, key, extraInfo)
}
// Important: computed runners must be run first so that computed getters
// can be invalidated before any normal runners that depend on them are run.
computedRunners.forEach(run)
runners.forEach(run)
}
function addRunners(
runners: Set<Autorun>,
computedRunners: Set<Autorun>,
runnersToAdd: Set<Autorun> | undefined
) {
if (runnersToAdd !== void 0) {
runnersToAdd.forEach(runner => {
if (runner.computed) {
computedRunners.add(runner)
} else {
runners.add(runner)
}
})
}
}
function scheduleRun(
runner: Autorun,
target: any,
type: OperationTypes,
key: string | symbol | undefined,
extraInfo: any
) {
if (__DEV__ && runner.onTrigger) {
runner.onTrigger(
Object.assign(
{
runner,
target,
key,
type
},
extraInfo
)
)
}
if (runner.scheduler !== void 0) {
runner.scheduler(runner)
} else {
runner()
}
}