mobx 7.0.0 → 7.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +24 -0
- package/dist/core/atom.d.ts +1 -1
- package/dist/core/computedvalue.d.ts +1 -1
- package/dist/core/globalstate.d.ts +7 -0
- package/dist/core/observable.d.ts +9 -1
- package/dist/mobx.cjs.development.js +127 -67
- package/dist/mobx.cjs.development.js.map +1 -1
- package/dist/mobx.cjs.production.min.js +1 -1
- package/dist/mobx.cjs.production.min.js.map +1 -1
- package/dist/mobx.esm.development.js +127 -67
- package/dist/mobx.esm.development.js.map +1 -1
- package/dist/mobx.esm.js +277 -215
- package/dist/mobx.esm.js.map +1 -1
- package/dist/mobx.esm.production.min.js +1 -1
- package/dist/mobx.esm.production.min.js.map +1 -1
- package/dist/mobx.mjs +277 -215
- package/dist/mobx.mjs.map +1 -1
- package/dist/mobx.umd.development.js +127 -67
- package/dist/mobx.umd.development.js.map +1 -1
- package/dist/mobx.umd.production.min.js +1 -1
- package/dist/mobx.umd.production.min.js.map +1 -1
- package/package.json +1 -1
- package/src/core/atom.ts +2 -1
- package/src/core/computedvalue.ts +9 -2
- package/src/core/derivation.ts +1 -1
- package/src/core/globalstate.ts +8 -0
- package/src/core/observable.ts +65 -25
- package/src/types/modifiers.ts +5 -0
- package/src/types/observableset.ts +40 -35
package/package.json
CHANGED
package/src/core/atom.ts
CHANGED
|
@@ -30,7 +30,8 @@ const enum AtomFlags {
|
|
|
30
30
|
export class Atom implements IAtom {
|
|
31
31
|
private flags_ = 0b000
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
// Allocated lazily on first observer to save memory.
|
|
34
|
+
observers_: Set<IDerivation> | null = null
|
|
34
35
|
|
|
35
36
|
lastAccessedBy_ = 0
|
|
36
37
|
lowestObserverState_ = IDerivationState_.NOT_TRACKING_
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
globalState,
|
|
15
15
|
isCaughtException,
|
|
16
16
|
isSpyEnabled,
|
|
17
|
+
markObserved,
|
|
17
18
|
propagateChangeConfirmed,
|
|
18
19
|
propagateMaybeChanged,
|
|
19
20
|
reportObserved,
|
|
@@ -84,7 +85,8 @@ export class ComputedValue<T> implements IObservable, IComputedValue<T>, IDeriva
|
|
|
84
85
|
dependenciesState_ = IDerivationState_.NOT_TRACKING_
|
|
85
86
|
observing_: IObservable[] = [] // nodes we are looking at. Our value depends on these nodes
|
|
86
87
|
newObserving_ = null // during tracking it's an array with new observed observers
|
|
87
|
-
|
|
88
|
+
// Lazily allocated on first observer - see Atom.observers_.
|
|
89
|
+
observers_: Set<IDerivation> | null = null
|
|
88
90
|
runId_ = 0
|
|
89
91
|
lastAccessedBy_ = 0
|
|
90
92
|
lowestObserverState_ = IDerivationState_.UP_TO_DATE_
|
|
@@ -200,7 +202,7 @@ export class ComputedValue<T> implements IObservable, IComputedValue<T>, IDeriva
|
|
|
200
202
|
if (
|
|
201
203
|
globalState.inBatch === 0 &&
|
|
202
204
|
// !globalState.trackingDerivatpion &&
|
|
203
|
-
this.observers_.size === 0 &&
|
|
205
|
+
(!this.observers_ || this.observers_.size === 0) &&
|
|
204
206
|
!this.keepAlive_
|
|
205
207
|
) {
|
|
206
208
|
if (shouldCompute(this)) {
|
|
@@ -210,6 +212,7 @@ export class ComputedValue<T> implements IObservable, IComputedValue<T>, IDeriva
|
|
|
210
212
|
endBatch()
|
|
211
213
|
}
|
|
212
214
|
} else {
|
|
215
|
+
const wasBeingObserved = this.isBeingObserved
|
|
213
216
|
reportObserved(this)
|
|
214
217
|
if (shouldCompute(this)) {
|
|
215
218
|
let prevTrackingContext = globalState.trackingContext
|
|
@@ -220,6 +223,10 @@ export class ComputedValue<T> implements IObservable, IComputedValue<T>, IDeriva
|
|
|
220
223
|
propagateChangeConfirmed(this)
|
|
221
224
|
}
|
|
222
225
|
globalState.trackingContext = prevTrackingContext
|
|
226
|
+
} else if (!wasBeingObserved && this.isBeingObserved) {
|
|
227
|
+
// We just became observed while serving a cached value, so the getter
|
|
228
|
+
// won't run and won't re-report our dependencies. Cascade to them. #4547
|
|
229
|
+
this.observing_.forEach(markObserved)
|
|
223
230
|
}
|
|
224
231
|
}
|
|
225
232
|
const result = this.value_!
|
package/src/core/derivation.ts
CHANGED
|
@@ -134,7 +134,7 @@ export function checkIfStateModificationsAreAllowed(atom: IAtom) {
|
|
|
134
134
|
if (!__DEV__) {
|
|
135
135
|
return
|
|
136
136
|
}
|
|
137
|
-
const hasObservers = atom.observers_.size > 0
|
|
137
|
+
const hasObservers = !!atom.observers_ && atom.observers_.size > 0
|
|
138
138
|
// Should not be possible to change observed state outside strict mode, except during initialization, see #563
|
|
139
139
|
if (
|
|
140
140
|
!globalState.allowStateChanges &&
|
package/src/core/globalstate.ts
CHANGED
|
@@ -82,6 +82,14 @@ export class MobXGlobals {
|
|
|
82
82
|
*/
|
|
83
83
|
isRunningReactions = false
|
|
84
84
|
|
|
85
|
+
/**
|
|
86
|
+
* Are we currently draining pendingUnobservations in endBatch?
|
|
87
|
+
* An onBecomeUnobserved handler can dispose a Reaction, which calls
|
|
88
|
+
* startBatch/endBatch again; this guards against re-entering the same
|
|
89
|
+
* drain loop recursively (see endBatch in observable.ts).
|
|
90
|
+
*/
|
|
91
|
+
isRunningUnobservations = false
|
|
92
|
+
|
|
85
93
|
/**
|
|
86
94
|
* Is it allowed to change observables at this point?
|
|
87
95
|
* In general, MobX doesn't allow that when running computations and React.render.
|
package/src/core/observable.ts
CHANGED
|
@@ -26,7 +26,7 @@ export interface IObservable extends IDepTreeNode {
|
|
|
26
26
|
lowestObserverState_: IDerivationState_ // Used to avoid redundant propagations
|
|
27
27
|
isPendingUnobservation: boolean // Used to push itself to global.pendingUnobservations at most once per batch.
|
|
28
28
|
|
|
29
|
-
observers_: Set<IDerivation>
|
|
29
|
+
observers_: Set<IDerivation> | null
|
|
30
30
|
|
|
31
31
|
onBUO(): void
|
|
32
32
|
onBO(): void
|
|
@@ -36,11 +36,11 @@ export interface IObservable extends IDepTreeNode {
|
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
export function hasObservers(observable: IObservable): boolean {
|
|
39
|
-
return observable.observers_ && observable.observers_.size > 0
|
|
39
|
+
return !!observable.observers_ && observable.observers_.size > 0
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
export function getObservers(observable: IObservable): Set<IDerivation> {
|
|
43
|
-
return observable.observers_
|
|
43
|
+
return observable.observers_ ?? new Set()
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
// function invariantObservers(observable: IObservable) {
|
|
@@ -65,7 +65,7 @@ export function addObserver(observable: IObservable, node: IDerivation) {
|
|
|
65
65
|
// invariant(observable._observers.indexOf(node) === -1, "INTERNAL ERROR add already added node");
|
|
66
66
|
// invariantObservers(observable);
|
|
67
67
|
|
|
68
|
-
observable.observers_.add(node)
|
|
68
|
+
;(observable.observers_ ??= new Set()).add(node)
|
|
69
69
|
if (observable.lowestObserverState_ > node.dependenciesState_) {
|
|
70
70
|
observable.lowestObserverState_ = node.dependenciesState_
|
|
71
71
|
}
|
|
@@ -78,8 +78,12 @@ export function removeObserver(observable: IObservable, node: IDerivation) {
|
|
|
78
78
|
// invariant(globalState.inBatch > 0, "INTERNAL ERROR, remove should be called only inside batch");
|
|
79
79
|
// invariant(observable._observers.indexOf(node) !== -1, "INTERNAL ERROR remove already removed node");
|
|
80
80
|
// invariantObservers(observable);
|
|
81
|
-
observable.observers_
|
|
82
|
-
if (
|
|
81
|
+
const observers = observable.observers_
|
|
82
|
+
if (!observers) {
|
|
83
|
+
return
|
|
84
|
+
}
|
|
85
|
+
observers.delete(node)
|
|
86
|
+
if (observers.size === 0) {
|
|
83
87
|
// deleting last observer
|
|
84
88
|
queueForUnobservation(observable)
|
|
85
89
|
}
|
|
@@ -108,27 +112,60 @@ export function endBatch() {
|
|
|
108
112
|
if (--globalState.inBatch === 0) {
|
|
109
113
|
runReactions()
|
|
110
114
|
// the batch is actually about to finish, all unobserving should happen here.
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
115
|
+
// Guard against re-entering this loop: an onBUO handler can dispose a Reaction,
|
|
116
|
+
// which calls startBatch/endBatch again while we're still iterating. Bail out of
|
|
117
|
+
// the nested call instead of recursing; the outer loop re-reads list.length on
|
|
118
|
+
// every iteration, so it picks up anything the nested dispose() pushes onto the
|
|
119
|
+
// same pendingUnobservations array.
|
|
120
|
+
if (!globalState.isRunningUnobservations) {
|
|
121
|
+
globalState.isRunningUnobservations = true
|
|
122
|
+
try {
|
|
123
|
+
const list = globalState.pendingUnobservations
|
|
124
|
+
for (let i = 0; i < list.length; i++) {
|
|
125
|
+
const observable = list[i]
|
|
126
|
+
observable.isPendingUnobservation = false
|
|
127
|
+
if (!observable.observers_ || observable.observers_.size === 0) {
|
|
128
|
+
if (observable.isBeingObserved) {
|
|
129
|
+
// if this observable had reactive observers, trigger the hooks
|
|
130
|
+
observable.isBeingObserved = false
|
|
131
|
+
observable.onBUO()
|
|
132
|
+
}
|
|
133
|
+
if (observable instanceof ComputedValue) {
|
|
134
|
+
// computed values are automatically teared down when the last observer leaves
|
|
135
|
+
// this process happens recursively, this computed might be the last observabe of another, etc..
|
|
136
|
+
observable.suspend_()
|
|
137
|
+
}
|
|
138
|
+
}
|
|
125
139
|
}
|
|
140
|
+
globalState.pendingUnobservations = []
|
|
141
|
+
} finally {
|
|
142
|
+
// Always release the guard, even if an onBUO handler (user code) threw,
|
|
143
|
+
// otherwise every future endBatch() would see isRunningUnobservations
|
|
144
|
+
// stuck true and silently stop draining pendingUnobservations forever.
|
|
145
|
+
globalState.isRunningUnobservations = false
|
|
126
146
|
}
|
|
127
147
|
}
|
|
128
|
-
globalState.pendingUnobservations = []
|
|
129
148
|
}
|
|
130
149
|
}
|
|
131
150
|
|
|
151
|
+
/**
|
|
152
|
+
* Marks an observable as observed, cascading into the dependencies of a ComputedValue.
|
|
153
|
+
* Unobservation already cascades (`suspend_` -> `clearObserving`), observation normally
|
|
154
|
+
* only does so by accident: a newly observed computed usually recomputes and re-reports
|
|
155
|
+
* its dependencies. When it serves a cached value instead nothing re-reports them, so the
|
|
156
|
+
* transition has to be propagated by hand. See #4547.
|
|
157
|
+
*/
|
|
158
|
+
export function markObserved(observable: IObservable) {
|
|
159
|
+
if (observable.isBeingObserved) {
|
|
160
|
+
return
|
|
161
|
+
}
|
|
162
|
+
observable.isBeingObserved = true
|
|
163
|
+
observable.onBO()
|
|
164
|
+
// No queueForUnobservation here: the observer links already exist, so the regular
|
|
165
|
+
// suspend_ -> clearObserving -> removeObserver teardown still delivers the onBUO.
|
|
166
|
+
observable.observing_?.forEach(markObserved)
|
|
167
|
+
}
|
|
168
|
+
|
|
132
169
|
export function reportObserved(observable: IObservable): boolean {
|
|
133
170
|
checkIfStateReadsAreAllowed(observable)
|
|
134
171
|
|
|
@@ -149,7 +186,10 @@ export function reportObserved(observable: IObservable): boolean {
|
|
|
149
186
|
}
|
|
150
187
|
}
|
|
151
188
|
return observable.isBeingObserved
|
|
152
|
-
} else if (
|
|
189
|
+
} else if (
|
|
190
|
+
(!observable.observers_ || observable.observers_.size === 0) &&
|
|
191
|
+
globalState.inBatch > 0
|
|
192
|
+
) {
|
|
153
193
|
queueForUnobservation(observable)
|
|
154
194
|
}
|
|
155
195
|
|
|
@@ -187,7 +227,7 @@ export function propagateChanged(observable: IObservable) {
|
|
|
187
227
|
observable.lowestObserverState_ = IDerivationState_.STALE_
|
|
188
228
|
|
|
189
229
|
// Ideally we use for..of here, but the downcompiled version is really slow...
|
|
190
|
-
observable.observers_
|
|
230
|
+
observable.observers_?.forEach(d => {
|
|
191
231
|
if (d.dependenciesState_ === IDerivationState_.UP_TO_DATE_) {
|
|
192
232
|
d.onBecomeStale_()
|
|
193
233
|
}
|
|
@@ -204,7 +244,7 @@ export function propagateChangeConfirmed(observable: IObservable) {
|
|
|
204
244
|
}
|
|
205
245
|
observable.lowestObserverState_ = IDerivationState_.STALE_
|
|
206
246
|
|
|
207
|
-
observable.observers_
|
|
247
|
+
observable.observers_?.forEach(d => {
|
|
208
248
|
if (d.dependenciesState_ === IDerivationState_.POSSIBLY_STALE_) {
|
|
209
249
|
d.dependenciesState_ = IDerivationState_.STALE_
|
|
210
250
|
} else if (
|
|
@@ -224,7 +264,7 @@ export function propagateMaybeChanged(observable: IObservable) {
|
|
|
224
264
|
}
|
|
225
265
|
observable.lowestObserverState_ = IDerivationState_.POSSIBLY_STALE_
|
|
226
266
|
|
|
227
|
-
observable.observers_
|
|
267
|
+
observable.observers_?.forEach(d => {
|
|
228
268
|
if (d.dependenciesState_ === IDerivationState_.UP_TO_DATE_) {
|
|
229
269
|
d.dependenciesState_ = IDerivationState_.POSSIBLY_STALE_
|
|
230
270
|
d.onBecomeStale_()
|
package/src/types/modifiers.ts
CHANGED
|
@@ -22,6 +22,11 @@ export interface IEnhancer<T> {
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
export function deepEnhancer(v, _, name) {
|
|
25
|
+
// primitives can never be made observable; skip the type checks below
|
|
26
|
+
if (v === null || (typeof v !== "object" && typeof v !== "function")) {
|
|
27
|
+
return v
|
|
28
|
+
}
|
|
29
|
+
|
|
25
30
|
// it is an observable already, done
|
|
26
31
|
if (isObservable(v)) {
|
|
27
32
|
return v
|
|
@@ -233,21 +233,11 @@ export class ObservableSet<T = any> implements Set<T>, IInterceptable<ISetWillCh
|
|
|
233
233
|
}
|
|
234
234
|
|
|
235
235
|
intersection<U>(otherSet: ReadonlySetLike<U> | Set<U>): Set<T & U> {
|
|
236
|
-
|
|
237
|
-
return otherSet.intersection(this)
|
|
238
|
-
} else {
|
|
239
|
-
const dehancedSet = new Set(this)
|
|
240
|
-
return dehancedSet.intersection(otherSet)
|
|
241
|
-
}
|
|
236
|
+
return new Set(this).intersection(otherSet)
|
|
242
237
|
}
|
|
243
238
|
|
|
244
239
|
union<U>(otherSet: ReadonlySetLike<U> | Set<U>): Set<T | U> {
|
|
245
|
-
|
|
246
|
-
return otherSet.union(this)
|
|
247
|
-
} else {
|
|
248
|
-
const dehancedSet = new Set(this)
|
|
249
|
-
return dehancedSet.union(otherSet)
|
|
250
|
-
}
|
|
240
|
+
return new Set(this).union(otherSet)
|
|
251
241
|
}
|
|
252
242
|
|
|
253
243
|
difference<U>(otherSet: ReadonlySetLike<U>): Set<T> {
|
|
@@ -255,12 +245,7 @@ export class ObservableSet<T = any> implements Set<T>, IInterceptable<ISetWillCh
|
|
|
255
245
|
}
|
|
256
246
|
|
|
257
247
|
symmetricDifference<U>(otherSet: ReadonlySetLike<U> | Set<U>): Set<T | U> {
|
|
258
|
-
|
|
259
|
-
return otherSet.symmetricDifference(this)
|
|
260
|
-
} else {
|
|
261
|
-
const dehancedSet = new Set(this)
|
|
262
|
-
return dehancedSet.symmetricDifference(otherSet)
|
|
263
|
-
}
|
|
248
|
+
return new Set(this).symmetricDifference(otherSet)
|
|
264
249
|
}
|
|
265
250
|
|
|
266
251
|
isSubsetOf(otherSet: ReadonlySetLike<unknown>): boolean {
|
|
@@ -272,12 +257,7 @@ export class ObservableSet<T = any> implements Set<T>, IInterceptable<ISetWillCh
|
|
|
272
257
|
}
|
|
273
258
|
|
|
274
259
|
isDisjointFrom(otherSet: ReadonlySetLike<unknown> | Set<unknown>): boolean {
|
|
275
|
-
|
|
276
|
-
return otherSet.isDisjointFrom(this)
|
|
277
|
-
} else {
|
|
278
|
-
const dehancedSet = new Set(this)
|
|
279
|
-
return dehancedSet.isDisjointFrom(otherSet)
|
|
280
|
-
}
|
|
260
|
+
return new Set(this).isDisjointFrom(otherSet)
|
|
281
261
|
}
|
|
282
262
|
|
|
283
263
|
replace(other: ObservableSet<T> | IObservableSetInitialValues<T>): ObservableSet<T> {
|
|
@@ -285,17 +265,42 @@ export class ObservableSet<T = any> implements Set<T>, IInterceptable<ISetWillCh
|
|
|
285
265
|
other = new Set(other)
|
|
286
266
|
}
|
|
287
267
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
268
|
+
if (Array.isArray(other) || isES6Set(other)) {
|
|
269
|
+
// Only emit `delete`/`add` events (and `reportChanged`) for values that
|
|
270
|
+
// actually change, instead of clearing and re-adding everything. `add` and
|
|
271
|
+
// `delete` are already no-ops for values that are respectively already
|
|
272
|
+
// present or already absent, so we just need to avoid deleting values that
|
|
273
|
+
// are part of the replacement. See #3761.
|
|
274
|
+
transaction(() => {
|
|
275
|
+
// Collect the desired values for quick lookup. `other` is already a Set
|
|
276
|
+
// here when it was passed (or snapshotted from an observable set) as one,
|
|
277
|
+
// so reuse it rather than allocating another; arrays are wrapped (which
|
|
278
|
+
// also dedupes them).
|
|
279
|
+
const replacementValues: Set<T> = isES6Set(other)
|
|
280
|
+
? other
|
|
281
|
+
: new Set<T>(other as Iterable<T>)
|
|
282
|
+
// Short-circuit the trivial cases: an empty replacement is just a clear,
|
|
283
|
+
// and replacing into an empty set only needs the adds.
|
|
284
|
+
if (replacementValues.size === 0) {
|
|
285
|
+
this.clear()
|
|
286
|
+
return
|
|
287
|
+
}
|
|
288
|
+
if (this.data_.size === 0) {
|
|
289
|
+
replacementValues.forEach(value => this.add(value))
|
|
290
|
+
return
|
|
291
|
+
}
|
|
292
|
+
// Delete values that are not part of the replacement.
|
|
293
|
+
for (const value of this.data_.values()) {
|
|
294
|
+
if (!replacementValues.has(this.dehanceValue_(value))) {
|
|
295
|
+
this.delete(value)
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
// Add new values; values that are already present are a no-op.
|
|
299
|
+
replacementValues.forEach(value => this.add(value))
|
|
300
|
+
})
|
|
301
|
+
} else if (other !== null && other !== undefined) {
|
|
302
|
+
die(41, other)
|
|
303
|
+
}
|
|
299
304
|
|
|
300
305
|
return this
|
|
301
306
|
}
|