vue3-yuanma/packages/reactivity/src/effect.ts

223 lines
5.1 KiB
TypeScript
Raw Normal View History

2018-11-14 00:03:35 +08:00
import { OperationTypes } from './operations'
2019-05-29 17:36:53 +08:00
import { Dep, targetMap } from './state'
2019-06-12 00:03:50 +08:00
import { EMPTY_OBJ } from '@vue/shared'
2018-11-14 00:03:35 +08:00
export interface ReactiveEffect {
(): any
isEffect: true
active: boolean
raw: Function
deps: Array<Dep>
computed?: boolean
scheduler?: Scheduler
onTrack?: Debugger
onTrigger?: Debugger
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
2018-11-14 00:03:35 +08:00
scheduler?: Scheduler
onTrack?: Debugger
onTrigger?: Debugger
2019-06-06 13:25:05 +08:00
onStop?: () => void
2018-11-14 00:03:35 +08:00
}
export type Scheduler = (run: () => any) => void
export interface DebuggerEvent {
2018-11-14 00:03:35 +08:00
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')
2019-06-12 00:03:50 +08:00
export function effect(
fn: Function,
options: ReactiveEffectOptions = EMPTY_OBJ
): ReactiveEffect {
if ((fn as ReactiveEffect).isEffect) {
fn = (fn as ReactiveEffect).raw
}
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
}
}
function createReactiveEffect(
2018-11-14 00:03:35 +08:00
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
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
}
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()
}
}
}
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
}
}
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
}
2019-05-29 17:36:53 +08:00
let depsMap = targetMap.get(target)
if (depsMap === void 0) {
targetMap.set(target, (depsMap = new Map()))
}
2018-11-14 00:03:35 +08:00
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
) {
2019-05-29 17:36:53 +08:00
const depsMap = targetMap.get(target)
if (depsMap === void 0) {
// never been tracked
return
}
const effects: Set<ReactiveEffect> = new Set()
const computedRunners: Set<ReactiveEffect> = new Set()
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) {
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()
}
}