refactor: rename things
This commit is contained in:
@@ -1,180 +0,0 @@
|
||||
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()
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { observable, immutable, unwrap } from './index'
|
||||
import { OperationTypes } from './operations'
|
||||
import { track, trigger } from './autorun'
|
||||
import { track, trigger } from './effect'
|
||||
import { LOCKED } from './lock'
|
||||
import { isObject } from '@vue/shared'
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { unwrap, observable, immutable } from './index'
|
||||
import { track, trigger } from './autorun'
|
||||
import { track, trigger } from './effect'
|
||||
import { OperationTypes } from './operations'
|
||||
import { LOCKED } from './lock'
|
||||
import { isObject } from '@vue/shared'
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { autorun } from './index'
|
||||
import { Autorun, activeAutorunStack } from './autorun'
|
||||
import { effect } from './index'
|
||||
import { ReactiveEffect, activeReactiveEffectStack } from './effect'
|
||||
|
||||
export interface ComputedGetter<T = any> {
|
||||
(): T
|
||||
runner: Autorun
|
||||
effect: ReactiveEffect
|
||||
}
|
||||
|
||||
export function computed<T, C = null>(
|
||||
@@ -12,7 +12,7 @@ export function computed<T, C = null>(
|
||||
): ComputedGetter<T> {
|
||||
let dirty: boolean = true
|
||||
let value: any = undefined
|
||||
const runner = autorun(() => getter.call(context, context), {
|
||||
const runner = effect(() => getter.call(context, context), {
|
||||
lazy: true,
|
||||
scheduler: () => {
|
||||
dirty = true
|
||||
@@ -23,21 +23,22 @@ export function computed<T, C = null>(
|
||||
value = runner()
|
||||
dirty = false
|
||||
}
|
||||
// When computed autoruns are accessed in a parent autorun, the parent
|
||||
// When computed effects are accessed in a parent effect, the parent
|
||||
// should track all the dependencies the computed property has tracked.
|
||||
// This should also apply for chained computed properties.
|
||||
trackChildRun(runner)
|
||||
return value
|
||||
}) as ComputedGetter
|
||||
// expose runner so computed can be stopped
|
||||
computedGetter.runner = runner
|
||||
// mark runner as computed so that it gets priority during trigger
|
||||
// expose effect so computed can be stopped
|
||||
computedGetter.effect = runner
|
||||
// mark effect as computed so that it gets priority during trigger
|
||||
runner.computed = true
|
||||
return computedGetter
|
||||
}
|
||||
|
||||
function trackChildRun(childRunner: Autorun) {
|
||||
const parentRunner = activeAutorunStack[activeAutorunStack.length - 1]
|
||||
function trackChildRun(childRunner: ReactiveEffect) {
|
||||
const parentRunner =
|
||||
activeReactiveEffectStack[activeReactiveEffectStack.length - 1]
|
||||
if (parentRunner) {
|
||||
for (let i = 0; i < childRunner.deps.length; i++) {
|
||||
const dep = childRunner.deps[i]
|
||||
|
||||
183
packages/observer/src/effect.ts
Normal file
183
packages/observer/src/effect.ts
Normal file
@@ -0,0 +1,183 @@
|
||||
import { OperationTypes } from './operations'
|
||||
import { Dep, KeyToDepMap, targetMap } from './state'
|
||||
|
||||
export interface ReactiveEffect {
|
||||
(): any
|
||||
isEffect: true
|
||||
active: boolean
|
||||
raw: Function
|
||||
deps: Array<Dep>
|
||||
computed?: boolean
|
||||
scheduler?: Scheduler
|
||||
onTrack?: Debugger
|
||||
onTrigger?: Debugger
|
||||
}
|
||||
|
||||
export interface ReactiveEffectOptions {
|
||||
lazy?: boolean
|
||||
scheduler?: Scheduler
|
||||
onTrack?: Debugger
|
||||
onTrigger?: Debugger
|
||||
}
|
||||
|
||||
export type Scheduler = (run: () => any) => void
|
||||
|
||||
export type DebuggerEvent = {
|
||||
effect: ReactiveEffect
|
||||
target: any
|
||||
type: OperationTypes
|
||||
key: string | symbol | undefined
|
||||
}
|
||||
|
||||
export type Debugger = (event: DebuggerEvent) => void
|
||||
|
||||
export const activeReactiveEffectStack: ReactiveEffect[] = []
|
||||
|
||||
export const ITERATE_KEY = Symbol('iterate')
|
||||
|
||||
export function createReactiveEffect(
|
||||
fn: Function,
|
||||
options: ReactiveEffectOptions
|
||||
): ReactiveEffect {
|
||||
const effect = function effect(...args): any {
|
||||
return run(effect as ReactiveEffect, fn, args)
|
||||
} as ReactiveEffect
|
||||
effect.isEffect = true
|
||||
effect.active = true
|
||||
effect.raw = fn
|
||||
effect.scheduler = options.scheduler
|
||||
effect.onTrack = options.onTrack
|
||||
effect.onTrigger = options.onTrigger
|
||||
effect.deps = []
|
||||
return effect
|
||||
}
|
||||
|
||||
function run(effect: ReactiveEffect, fn: Function, args: any[]): any {
|
||||
if (!effect.active) {
|
||||
return fn(...args)
|
||||
}
|
||||
if (activeReactiveEffectStack.indexOf(effect) === -1) {
|
||||
cleanup(effect)
|
||||
try {
|
||||
activeReactiveEffectStack.push(effect)
|
||||
return fn(...args)
|
||||
} finally {
|
||||
activeReactiveEffectStack.pop()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function cleanup(effect: ReactiveEffect) {
|
||||
for (let i = 0; i < effect.deps.length; i++) {
|
||||
effect.deps[i].delete(effect)
|
||||
}
|
||||
effect.deps = []
|
||||
}
|
||||
|
||||
export function track(
|
||||
target: any,
|
||||
type: OperationTypes,
|
||||
key?: string | symbol
|
||||
) {
|
||||
const effect = activeReactiveEffectStack[activeReactiveEffectStack.length - 1]
|
||||
if (effect) {
|
||||
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(effect)) {
|
||||
dep.add(effect)
|
||||
effect.deps.push(dep)
|
||||
if (__DEV__ && effect.onTrack) {
|
||||
effect.onTrack({
|
||||
effect,
|
||||
target,
|
||||
type,
|
||||
key
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function trigger(
|
||||
target: any,
|
||||
type: OperationTypes,
|
||||
key?: string | symbol,
|
||||
extraInfo?: any
|
||||
) {
|
||||
const depsMap = targetMap.get(target) as KeyToDepMap
|
||||
const effects = new Set()
|
||||
const computedRunners = new Set()
|
||||
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) {
|
||||
addRunners(effects, 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(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,
|
||||
target: any,
|
||||
type: OperationTypes,
|
||||
key: string | symbol | undefined,
|
||||
extraInfo: any
|
||||
) {
|
||||
if (__DEV__ && effect.onTrigger) {
|
||||
effect.onTrigger(
|
||||
Object.assign(
|
||||
{
|
||||
effect,
|
||||
target,
|
||||
key,
|
||||
type
|
||||
},
|
||||
extraInfo
|
||||
)
|
||||
)
|
||||
}
|
||||
if (effect.scheduler !== void 0) {
|
||||
effect.scheduler(effect)
|
||||
} else {
|
||||
effect()
|
||||
}
|
||||
}
|
||||
@@ -17,14 +17,14 @@ import {
|
||||
} from './state'
|
||||
|
||||
import {
|
||||
createAutorun,
|
||||
createReactiveEffect,
|
||||
cleanup,
|
||||
Autorun,
|
||||
AutorunOptions,
|
||||
ReactiveEffect,
|
||||
ReactiveEffectOptions,
|
||||
DebuggerEvent
|
||||
} from './autorun'
|
||||
} from './effect'
|
||||
|
||||
export { Autorun, AutorunOptions, DebuggerEvent }
|
||||
export { ReactiveEffect, ReactiveEffectOptions, DebuggerEvent }
|
||||
export { OperationTypes } from './operations'
|
||||
export { computed, ComputedGetter } from './computed'
|
||||
export { lock, unlock } from './lock'
|
||||
@@ -114,24 +114,24 @@ function createObservable(
|
||||
return observed
|
||||
}
|
||||
|
||||
export function autorun(
|
||||
export function effect(
|
||||
fn: Function,
|
||||
options: AutorunOptions = EMPTY_OBJ
|
||||
): Autorun {
|
||||
if ((fn as Autorun).isAutorun) {
|
||||
fn = (fn as Autorun).raw
|
||||
options: ReactiveEffectOptions = EMPTY_OBJ
|
||||
): ReactiveEffect {
|
||||
if ((fn as ReactiveEffect).isEffect) {
|
||||
fn = (fn as ReactiveEffect).raw
|
||||
}
|
||||
const runner = createAutorun(fn, options)
|
||||
const effect = createReactiveEffect(fn, options)
|
||||
if (!options.lazy) {
|
||||
runner()
|
||||
effect()
|
||||
}
|
||||
return runner
|
||||
return effect
|
||||
}
|
||||
|
||||
export function stop(runner: Autorun) {
|
||||
if (runner.active) {
|
||||
cleanup(runner)
|
||||
runner.active = false
|
||||
export function stop(effect: ReactiveEffect) {
|
||||
if (effect.active) {
|
||||
cleanup(effect)
|
||||
effect.active = false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { Autorun } from './autorun'
|
||||
import { ReactiveEffect } from './effect'
|
||||
|
||||
// The main WeakMap that stores {target -> key -> dep} connections.
|
||||
// Conceptually, it's easier to think of a dependency as a Dep class
|
||||
// which maintains a Set of subscribers, but we simply store them as
|
||||
// raw Sets to reduce memory overhead.
|
||||
export type Dep = Set<Autorun>
|
||||
export type Dep = Set<ReactiveEffect>
|
||||
export type KeyToDepMap = Map<string | symbol, Dep>
|
||||
export const targetMap: WeakMap<any, KeyToDepMap> = new WeakMap()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user