|
@@ -1,5 +1,5 @@
|
|
|
import { TrackOpTypes, TriggerOpTypes } from './operations'
|
|
import { TrackOpTypes, TriggerOpTypes } from './operations'
|
|
|
-import { EMPTY_OBJ, extend, isArray, isIntegerKey, isMap } from '@vue/shared'
|
|
|
|
|
|
|
+import { extend, isArray, isIntegerKey, isMap } from '@vue/shared'
|
|
|
|
|
|
|
|
// The main WeakMap that stores {target -> key -> dep} connections.
|
|
// The main WeakMap that stores {target -> key -> dep} connections.
|
|
|
// Conceptually, it's easier to think of a dependency as a Dep class
|
|
// Conceptually, it's easier to think of a dependency as a Dep class
|
|
@@ -9,40 +9,7 @@ type Dep = Set<ReactiveEffect>
|
|
|
type KeyToDepMap = Map<any, Dep>
|
|
type KeyToDepMap = Map<any, Dep>
|
|
|
const targetMap = new WeakMap<any, KeyToDepMap>()
|
|
const targetMap = new WeakMap<any, KeyToDepMap>()
|
|
|
|
|
|
|
|
-export interface ReactiveEffect<T = any> {
|
|
|
|
|
- (): T
|
|
|
|
|
- _isEffect: true
|
|
|
|
|
- id: number
|
|
|
|
|
- active: boolean
|
|
|
|
|
- raw: () => T
|
|
|
|
|
- deps: Array<Dep>
|
|
|
|
|
- options: ReactiveEffectOptions
|
|
|
|
|
- allowRecurse: boolean
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-export interface ReactiveEffectOptions {
|
|
|
|
|
- lazy?: boolean
|
|
|
|
|
- scheduler?: (job: ReactiveEffect) => void
|
|
|
|
|
- onTrack?: (event: DebuggerEvent) => void
|
|
|
|
|
- onTrigger?: (event: DebuggerEvent) => void
|
|
|
|
|
- onStop?: () => void
|
|
|
|
|
- /**
|
|
|
|
|
- * Indicates whether the job is allowed to recursively trigger itself when
|
|
|
|
|
- * managed by the scheduler.
|
|
|
|
|
- *
|
|
|
|
|
- * By default, a job cannot trigger itself because some built-in method calls,
|
|
|
|
|
- * e.g. Array.prototype.push actually performs reads as well (#1740) which
|
|
|
|
|
- * can lead to confusing infinite loops.
|
|
|
|
|
- * The allowed cases are component update functions and watch callbacks.
|
|
|
|
|
- * Component update functions may update child component props, which in turn
|
|
|
|
|
- * trigger flush: "pre" watch callbacks that mutates state that the parent
|
|
|
|
|
- * relies on (#1801). Watch callbacks doesn't track its dependencies so if it
|
|
|
|
|
- * triggers itself again, it's likely intentional and it is the user's
|
|
|
|
|
- * responsibility to perform recursive state mutation that eventually
|
|
|
|
|
- * stabilizes (#1727).
|
|
|
|
|
- */
|
|
|
|
|
- allowRecurse?: boolean
|
|
|
|
|
-}
|
|
|
|
|
|
|
+export type EffectScheduler = () => void
|
|
|
|
|
|
|
|
export type DebuggerEvent = {
|
|
export type DebuggerEvent = {
|
|
|
effect: ReactiveEffect
|
|
effect: ReactiveEffect
|
|
@@ -62,52 +29,34 @@ let activeEffect: ReactiveEffect | undefined
|
|
|
|
|
|
|
|
export const ITERATE_KEY = Symbol(__DEV__ ? 'iterate' : '')
|
|
export const ITERATE_KEY = Symbol(__DEV__ ? 'iterate' : '')
|
|
|
export const MAP_KEY_ITERATE_KEY = Symbol(__DEV__ ? 'Map key iterate' : '')
|
|
export const MAP_KEY_ITERATE_KEY = Symbol(__DEV__ ? 'Map key iterate' : '')
|
|
|
|
|
+export class ReactiveEffect<T = any> {
|
|
|
|
|
+ active = true
|
|
|
|
|
+ deps: Dep[] = []
|
|
|
|
|
|
|
|
-export function isEffect(fn: any): fn is ReactiveEffect {
|
|
|
|
|
- return fn && 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
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
|
|
+ // can be attached after creation
|
|
|
|
|
+ onStop?: () => void
|
|
|
|
|
+ // dev only
|
|
|
|
|
+ onTrack?: (event: DebuggerEvent) => void
|
|
|
|
|
+ // dev only
|
|
|
|
|
+ onTrigger?: (event: DebuggerEvent) => void
|
|
|
|
|
|
|
|
-let uid = 0
|
|
|
|
|
|
|
+ constructor(
|
|
|
|
|
+ public fn: () => T,
|
|
|
|
|
+ public scheduler: EffectScheduler | null = null,
|
|
|
|
|
+ // allow recursive self-invocation
|
|
|
|
|
+ public allowRecurse = false
|
|
|
|
|
+ ) {}
|
|
|
|
|
|
|
|
-function createReactiveEffect<T = any>(
|
|
|
|
|
- fn: () => T,
|
|
|
|
|
- options: ReactiveEffectOptions
|
|
|
|
|
-): ReactiveEffect<T> {
|
|
|
|
|
- const effect = function reactiveEffect(): unknown {
|
|
|
|
|
- if (!effect.active) {
|
|
|
|
|
- return fn()
|
|
|
|
|
|
|
+ run() {
|
|
|
|
|
+ if (!this.active) {
|
|
|
|
|
+ return this.fn()
|
|
|
}
|
|
}
|
|
|
- if (!effectStack.includes(effect)) {
|
|
|
|
|
- cleanup(effect)
|
|
|
|
|
|
|
+ if (!effectStack.includes(this)) {
|
|
|
|
|
+ this.cleanup()
|
|
|
try {
|
|
try {
|
|
|
enableTracking()
|
|
enableTracking()
|
|
|
- effectStack.push(effect)
|
|
|
|
|
- activeEffect = effect
|
|
|
|
|
- return fn()
|
|
|
|
|
|
|
+ effectStack.push((activeEffect = this))
|
|
|
|
|
+ return this.fn()
|
|
|
} finally {
|
|
} finally {
|
|
|
effectStack.pop()
|
|
effectStack.pop()
|
|
|
resetTracking()
|
|
resetTracking()
|
|
@@ -115,25 +64,65 @@ function createReactiveEffect<T = any>(
|
|
|
activeEffect = n > 0 ? effectStack[n - 1] : undefined
|
|
activeEffect = n > 0 ? effectStack[n - 1] : undefined
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
- } as ReactiveEffect
|
|
|
|
|
- effect.id = uid++
|
|
|
|
|
- effect.allowRecurse = !!options.allowRecurse
|
|
|
|
|
- effect._isEffect = true
|
|
|
|
|
- effect.active = true
|
|
|
|
|
- effect.raw = fn
|
|
|
|
|
- effect.deps = []
|
|
|
|
|
- effect.options = options
|
|
|
|
|
- return effect
|
|
|
|
|
-}
|
|
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
-function cleanup(effect: ReactiveEffect) {
|
|
|
|
|
- const { deps } = effect
|
|
|
|
|
- if (deps.length) {
|
|
|
|
|
- for (let i = 0; i < deps.length; i++) {
|
|
|
|
|
- deps[i].delete(effect)
|
|
|
|
|
|
|
+ cleanup() {
|
|
|
|
|
+ const { deps } = this
|
|
|
|
|
+ if (deps.length) {
|
|
|
|
|
+ for (let i = 0; i < deps.length; i++) {
|
|
|
|
|
+ deps[i].delete(this)
|
|
|
|
|
+ }
|
|
|
|
|
+ deps.length = 0
|
|
|
}
|
|
}
|
|
|
- deps.length = 0
|
|
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ stop() {
|
|
|
|
|
+ if (this.active) {
|
|
|
|
|
+ this.cleanup()
|
|
|
|
|
+ if (this.onStop) {
|
|
|
|
|
+ this.onStop()
|
|
|
|
|
+ }
|
|
|
|
|
+ this.active = false
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+export interface ReactiveEffectOptions {
|
|
|
|
|
+ lazy?: boolean
|
|
|
|
|
+ scheduler?: EffectScheduler
|
|
|
|
|
+ allowRecurse?: boolean
|
|
|
|
|
+ onStop?: () => void
|
|
|
|
|
+ onTrack?: (event: DebuggerEvent) => void
|
|
|
|
|
+ onTrigger?: (event: DebuggerEvent) => void
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+export interface ReactiveEffectRunner<T = any> {
|
|
|
|
|
+ (): T
|
|
|
|
|
+ effect: ReactiveEffect
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+export function effect<T = any>(
|
|
|
|
|
+ fn: () => T,
|
|
|
|
|
+ options?: ReactiveEffectOptions
|
|
|
|
|
+): ReactiveEffectRunner {
|
|
|
|
|
+ if ((fn as ReactiveEffectRunner).effect) {
|
|
|
|
|
+ fn = (fn as ReactiveEffectRunner).effect.fn
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const _effect = new ReactiveEffect(fn)
|
|
|
|
|
+ if (options) {
|
|
|
|
|
+ extend(_effect, options)
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!options || !options.lazy) {
|
|
|
|
|
+ _effect.run()
|
|
|
|
|
+ }
|
|
|
|
|
+ const runner = _effect.run.bind(_effect) as ReactiveEffectRunner
|
|
|
|
|
+ runner.effect = _effect
|
|
|
|
|
+ return runner
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+export function stop(runner: ReactiveEffectRunner) {
|
|
|
|
|
+ runner.effect.stop()
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
let shouldTrack = true
|
|
let shouldTrack = true
|
|
@@ -185,8 +174,8 @@ export function trackEffects(
|
|
|
if (!dep.has(activeEffect!)) {
|
|
if (!dep.has(activeEffect!)) {
|
|
|
dep.add(activeEffect!)
|
|
dep.add(activeEffect!)
|
|
|
activeEffect!.deps.push(dep)
|
|
activeEffect!.deps.push(dep)
|
|
|
- if (__DEV__ && activeEffect!.options.onTrack) {
|
|
|
|
|
- activeEffect!.options.onTrack(
|
|
|
|
|
|
|
+ if (__DEV__ && activeEffect!.onTrack) {
|
|
|
|
|
+ activeEffect!.onTrack(
|
|
|
Object.assign(
|
|
Object.assign(
|
|
|
{
|
|
{
|
|
|
effect: activeEffect!
|
|
effect: activeEffect!
|
|
@@ -284,13 +273,13 @@ export function triggerEffects(
|
|
|
// spread into array for stabilization
|
|
// spread into array for stabilization
|
|
|
for (const effect of [...dep]) {
|
|
for (const effect of [...dep]) {
|
|
|
if (effect !== activeEffect || effect.allowRecurse) {
|
|
if (effect !== activeEffect || effect.allowRecurse) {
|
|
|
- if (__DEV__ && effect.options.onTrigger) {
|
|
|
|
|
- effect.options.onTrigger(extend({ effect }, debuggerEventExtraInfo))
|
|
|
|
|
|
|
+ if (__DEV__ && effect.onTrigger) {
|
|
|
|
|
+ effect.onTrigger(extend({ effect }, debuggerEventExtraInfo))
|
|
|
}
|
|
}
|
|
|
- if (effect.options.scheduler) {
|
|
|
|
|
- effect.options.scheduler(effect)
|
|
|
|
|
|
|
+ if (effect.scheduler) {
|
|
|
|
|
+ effect.scheduler()
|
|
|
} else {
|
|
} else {
|
|
|
- effect()
|
|
|
|
|
|
|
+ effect.run()
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|