1
1
import { TrackOpTypes , TriggerOpTypes } from './operations'
2
- import { EMPTY_OBJ , extend , isArray , isIntegerKey , isMap } from '@vue/shared'
2
+ import { extend , isArray , isIntegerKey , isMap } from '@vue/shared'
3
3
4
4
// The main WeakMap that stores {target -> key -> dep} connections.
5
5
// Conceptually, it's easier to think of a dependency as a Dep class
@@ -9,40 +9,7 @@ type Dep = Set<ReactiveEffect>
9
9
type KeyToDepMap = Map < any , Dep >
10
10
const targetMap = new WeakMap < any , KeyToDepMap > ( )
11
11
12
- export interface ReactiveEffect < T = any > {
13
- ( ) : T
14
- _isEffect : true
15
- id : number
16
- active : boolean
17
- raw : ( ) => T
18
- deps : Array < Dep >
19
- options : ReactiveEffectOptions
20
- allowRecurse : boolean
21
- }
22
-
23
- export interface ReactiveEffectOptions {
24
- lazy ?: boolean
25
- scheduler ?: ( job : ReactiveEffect ) => void
26
- onTrack ?: ( event : DebuggerEvent ) => void
27
- onTrigger ?: ( event : DebuggerEvent ) => void
28
- onStop ?: ( ) => void
29
- /**
30
- * Indicates whether the job is allowed to recursively trigger itself when
31
- * managed by the scheduler.
32
- *
33
- * By default, a job cannot trigger itself because some built-in method calls,
34
- * e.g. Array.prototype.push actually performs reads as well (#1740) which
35
- * can lead to confusing infinite loops.
36
- * The allowed cases are component update functions and watch callbacks.
37
- * Component update functions may update child component props, which in turn
38
- * trigger flush: "pre" watch callbacks that mutates state that the parent
39
- * relies on (#1801). Watch callbacks doesn't track its dependencies so if it
40
- * triggers itself again, it's likely intentional and it is the user's
41
- * responsibility to perform recursive state mutation that eventually
42
- * stabilizes (#1727).
43
- */
44
- allowRecurse ?: boolean
45
- }
12
+ export type EffectScheduler = ( ) => void
46
13
47
14
export type DebuggerEvent = {
48
15
effect : ReactiveEffect
@@ -62,78 +29,100 @@ let activeEffect: ReactiveEffect | undefined
62
29
63
30
export const ITERATE_KEY = Symbol ( __DEV__ ? 'iterate' : '' )
64
31
export const MAP_KEY_ITERATE_KEY = Symbol ( __DEV__ ? 'Map key iterate' : '' )
32
+ export class ReactiveEffect < T = any > {
33
+ active = true
34
+ deps : Dep [ ] = [ ]
65
35
66
- export function isEffect ( fn : any ) : fn is ReactiveEffect {
67
- return fn && fn . _isEffect === true
68
- }
69
-
70
- export function effect < T = any > (
71
- fn : ( ) => T ,
72
- options : ReactiveEffectOptions = EMPTY_OBJ
73
- ) : ReactiveEffect < T > {
74
- if ( isEffect ( fn ) ) {
75
- fn = fn . raw
76
- }
77
- const effect = createReactiveEffect ( fn , options )
78
- if ( ! options . lazy ) {
79
- effect ( )
80
- }
81
- return effect
82
- }
83
-
84
- export function stop ( effect : ReactiveEffect ) {
85
- if ( effect . active ) {
86
- cleanup ( effect )
87
- if ( effect . options . onStop ) {
88
- effect . options . onStop ( )
89
- }
90
- effect . active = false
91
- }
92
- }
36
+ // can be attached after creation
37
+ onStop ?: ( ) => void
38
+ // dev only
39
+ onTrack ?: ( event : DebuggerEvent ) => void
40
+ // dev only
41
+ onTrigger ?: ( event : DebuggerEvent ) => void
93
42
94
- let uid = 0
43
+ constructor (
44
+ public fn : ( ) => T ,
45
+ public scheduler : EffectScheduler | null = null ,
46
+ // allow recursive self-invocation
47
+ public allowRecurse = false
48
+ ) { }
95
49
96
- function createReactiveEffect < T = any > (
97
- fn : ( ) => T ,
98
- options : ReactiveEffectOptions
99
- ) : ReactiveEffect < T > {
100
- const effect = function reactiveEffect ( ) : unknown {
101
- if ( ! effect . active ) {
102
- return fn ( )
50
+ run ( ) {
51
+ if ( ! this . active ) {
52
+ return this . fn ( )
103
53
}
104
- if ( ! effectStack . includes ( effect ) ) {
105
- cleanup ( effect )
54
+ if ( ! effectStack . includes ( this ) ) {
55
+ this . cleanup ( )
106
56
try {
107
57
enableTracking ( )
108
- effectStack . push ( effect )
109
- activeEffect = effect
110
- return fn ( )
58
+ effectStack . push ( ( activeEffect = this ) )
59
+ return this . fn ( )
111
60
} finally {
112
61
effectStack . pop ( )
113
62
resetTracking ( )
114
63
const n = effectStack . length
115
64
activeEffect = n > 0 ? effectStack [ n - 1 ] : undefined
116
65
}
117
66
}
118
- } as ReactiveEffect
119
- effect . id = uid ++
120
- effect . allowRecurse = ! ! options . allowRecurse
121
- effect . _isEffect = true
122
- effect . active = true
123
- effect . raw = fn
124
- effect . deps = [ ]
125
- effect . options = options
126
- return effect
127
- }
67
+ }
128
68
129
- function cleanup ( effect : ReactiveEffect ) {
130
- const { deps } = effect
131
- if ( deps . length ) {
132
- for ( let i = 0 ; i < deps . length ; i ++ ) {
133
- deps [ i ] . delete ( effect )
69
+ cleanup ( ) {
70
+ const { deps } = this
71
+ if ( deps . length ) {
72
+ for ( let i = 0 ; i < deps . length ; i ++ ) {
73
+ deps [ i ] . delete ( this )
74
+ }
75
+ deps . length = 0
134
76
}
135
- deps . length = 0
136
77
}
78
+
79
+ stop ( ) {
80
+ if ( this . active ) {
81
+ this . cleanup ( )
82
+ if ( this . onStop ) {
83
+ this . onStop ( )
84
+ }
85
+ this . active = false
86
+ }
87
+ }
88
+ }
89
+
90
+ export interface ReactiveEffectOptions {
91
+ lazy ?: boolean
92
+ scheduler ?: EffectScheduler
93
+ allowRecurse ?: boolean
94
+ onStop ?: ( ) => void
95
+ onTrack ?: ( event : DebuggerEvent ) => void
96
+ onTrigger ?: ( event : DebuggerEvent ) => void
97
+ }
98
+
99
+ export interface ReactiveEffectRunner < T = any > {
100
+ ( ) : T
101
+ effect : ReactiveEffect
102
+ }
103
+
104
+ export function effect < T = any > (
105
+ fn : ( ) => T ,
106
+ options ?: ReactiveEffectOptions
107
+ ) : ReactiveEffectRunner {
108
+ if ( ( fn as ReactiveEffectRunner ) . effect ) {
109
+ fn = ( fn as ReactiveEffectRunner ) . effect . fn
110
+ }
111
+
112
+ const _effect = new ReactiveEffect ( fn )
113
+ if ( options ) {
114
+ extend ( _effect , options )
115
+ }
116
+ if ( ! options || ! options . lazy ) {
117
+ _effect . run ( )
118
+ }
119
+ const runner = _effect . run . bind ( _effect ) as ReactiveEffectRunner
120
+ runner . effect = _effect
121
+ return runner
122
+ }
123
+
124
+ export function stop ( runner : ReactiveEffectRunner ) {
125
+ runner . effect . stop ( )
137
126
}
138
127
139
128
let shouldTrack = true
@@ -185,8 +174,8 @@ export function trackEffects(
185
174
if ( ! dep . has ( activeEffect ! ) ) {
186
175
dep . add ( activeEffect ! )
187
176
activeEffect ! . deps . push ( dep )
188
- if ( __DEV__ && activeEffect ! . options . onTrack ) {
189
- activeEffect ! . options . onTrack (
177
+ if ( __DEV__ && activeEffect ! . onTrack ) {
178
+ activeEffect ! . onTrack (
190
179
Object . assign (
191
180
{
192
181
effect : activeEffect !
@@ -284,13 +273,13 @@ export function triggerEffects(
284
273
// spread into array for stabilization
285
274
for ( const effect of [ ...dep ] ) {
286
275
if ( effect !== activeEffect || effect . allowRecurse ) {
287
- if ( __DEV__ && effect . options . onTrigger ) {
288
- effect . options . onTrigger ( extend ( { effect } , debuggerEventExtraInfo ) )
276
+ if ( __DEV__ && effect . onTrigger ) {
277
+ effect . onTrigger ( extend ( { effect } , debuggerEventExtraInfo ) )
289
278
}
290
- if ( effect . options . scheduler ) {
291
- effect . options . scheduler ( effect )
279
+ if ( effect . scheduler ) {
280
+ effect . scheduler ( )
292
281
} else {
293
- effect ( )
282
+ effect . run ( )
294
283
}
295
284
}
296
285
}
0 commit comments