| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231 |
- import { OperationTypes } from './operations'
- import { Dep, targetMap } from './reactive'
- import { EMPTY_OBJ, extend } from '@vue/shared'
- export interface ReactiveEffect {
- (): any
- isEffect: true
- active: boolean
- raw: Function
- deps: Array<Dep>
- computed?: boolean
- scheduler?: (run: Function) => void
- onTrack?: (event: DebuggerEvent) => void
- onTrigger?: (event: DebuggerEvent) => void
- onStop?: () => void
- }
- export interface ReactiveEffectOptions {
- lazy?: boolean
- computed?: boolean
- scheduler?: (run: Function) => void
- onTrack?: (event: DebuggerEvent) => void
- onTrigger?: (event: DebuggerEvent) => void
- onStop?: () => void
- }
- export interface DebuggerEvent {
- effect: ReactiveEffect
- target: any
- type: OperationTypes
- key: string | symbol | undefined
- }
- export const activeReactiveEffectStack: ReactiveEffect[] = []
- export const ITERATE_KEY = Symbol('iterate')
- 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(
- 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.onStop = options.onStop
- effect.computed = options.computed
- 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()
- }
- }
- }
- function cleanup(effect: ReactiveEffect) {
- const { deps } = effect
- if (deps.length) {
- for (let i = 0; i < deps.length; i++) {
- deps[i].delete(effect)
- }
- deps.length = 0
- }
- }
- let shouldTrack = true
- export function pauseTracking() {
- shouldTrack = false
- }
- export function resumeTracking() {
- shouldTrack = true
- }
- export function track(
- target: any,
- type: OperationTypes,
- key?: string | symbol
- ) {
- if (!shouldTrack) {
- return
- }
- const effect = activeReactiveEffectStack[activeReactiveEffectStack.length - 1]
- if (effect) {
- 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
- })
- }
- }
- }
- }
- export function trigger(
- target: any,
- type: OperationTypes,
- key?: string | symbol,
- extraInfo?: any
- ) {
- const depsMap = targetMap.get(target)
- if (depsMap === void 0) {
- // never been tracked
- return
- }
- const effects = new Set<ReactiveEffect>()
- const computedRunners = new Set<ReactiveEffect>()
- 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))
- }
- // 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(
- extend(
- {
- effect,
- target,
- key,
- type
- },
- extraInfo
- )
- )
- }
- if (effect.scheduler !== void 0) {
- effect.scheduler(effect)
- } else {
- effect()
- }
- }
|