| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239 |
- import { TrackOpTypes, TriggerOpTypes } from './operations'
- import { EMPTY_OBJ, extend, isArray } from '@vue/shared'
- // 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.
- type Dep = Set<ReactiveEffect>
- type KeyToDepMap = Map<any, Dep>
- const targetMap = new WeakMap<any, KeyToDepMap>()
- export interface ReactiveEffect<T = any> {
- (): T
- _isEffect: true
- active: boolean
- raw: () => T
- deps: Array<Dep>
- options: ReactiveEffectOptions
- }
- export interface ReactiveEffectOptions {
- lazy?: boolean
- computed?: boolean
- scheduler?: (run: Function) => void
- onTrack?: (event: DebuggerEvent) => void
- onTrigger?: (event: DebuggerEvent) => void
- onStop?: () => void
- }
- export type DebuggerEvent = {
- effect: ReactiveEffect
- target: object
- type: TrackOpTypes | TriggerOpTypes
- key: any
- } & DebuggerEventExtraInfo
- export interface DebuggerEventExtraInfo {
- newValue?: any
- oldValue?: any
- oldTarget?: Map<any, any> | Set<any>
- }
- const effectStack: ReactiveEffect[] = []
- export let activeEffect: ReactiveEffect | undefined
- export const ITERATE_KEY = Symbol('iterate')
- export function isEffect(fn: any): fn is ReactiveEffect {
- return fn != null && fn._isEffect === true
- }
- export function effect<T = any>(
- fn: () => T,
- options: ReactiveEffectOptions = EMPTY_OBJ
- ): ReactiveEffect<T> {
- if (isEffect(fn)) {
- fn = fn.raw
- }
- const effect = createReactiveEffect(fn, options)
- if (!options.lazy) {
- effect()
- }
- return effect
- }
- export function stop(effect: ReactiveEffect) {
- if (effect.active) {
- cleanup(effect)
- if (effect.options.onStop) {
- effect.options.onStop()
- }
- effect.active = false
- }
- }
- function createReactiveEffect<T = any>(
- fn: () => T,
- options: ReactiveEffectOptions
- ): ReactiveEffect<T> {
- const effect = function reactiveEffect(...args: unknown[]): unknown {
- return run(effect, fn, args)
- } as ReactiveEffect
- effect._isEffect = true
- effect.active = true
- effect.raw = fn
- effect.deps = []
- effect.options = options
- return effect
- }
- function run(effect: ReactiveEffect, fn: Function, args: unknown[]): unknown {
- if (!effect.active) {
- return fn(...args)
- }
- if (!effectStack.includes(effect)) {
- cleanup(effect)
- try {
- enableTracking()
- effectStack.push(effect)
- activeEffect = effect
- return fn(...args)
- } finally {
- effectStack.pop()
- resetTracking()
- activeEffect = effectStack[effectStack.length - 1]
- }
- }
- }
- 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
- const trackStack: boolean[] = []
- export function pauseTracking() {
- trackStack.push(shouldTrack)
- shouldTrack = false
- }
- export function enableTracking() {
- trackStack.push(shouldTrack)
- shouldTrack = true
- }
- export function resetTracking() {
- const last = trackStack.pop()
- shouldTrack = last === undefined ? true : last
- }
- export function track(target: object, type: TrackOpTypes, key: unknown) {
- if (!shouldTrack || activeEffect === undefined) {
- return
- }
- 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(activeEffect)) {
- dep.add(activeEffect)
- activeEffect.deps.push(dep)
- if (__DEV__ && activeEffect.options.onTrack) {
- activeEffect.options.onTrack({
- effect: activeEffect,
- target,
- type,
- key
- })
- }
- }
- }
- export function trigger(
- target: object,
- type: TriggerOpTypes,
- key?: unknown,
- extraInfo?: DebuggerEventExtraInfo
- ) {
- 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 === TriggerOpTypes.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 === TriggerOpTypes.ADD || type === TriggerOpTypes.DELETE) {
- const iterationKey = 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.options.computed) {
- computedRunners.add(effect)
- } else {
- effects.add(effect)
- }
- })
- }
- }
- function scheduleRun(
- effect: ReactiveEffect,
- target: object,
- type: TriggerOpTypes,
- key: unknown,
- extraInfo?: DebuggerEventExtraInfo
- ) {
- if (__DEV__ && effect.options.onTrigger) {
- const event: DebuggerEvent = {
- effect,
- target,
- key,
- type
- }
- effect.options.onTrigger(extraInfo ? extend(event, extraInfo) : event)
- }
- if (effect.options.scheduler !== void 0) {
- effect.options.scheduler(effect)
- } else {
- effect()
- }
- }
|