mobx 6.10.0 → 6.10.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.
@@ -2,3 +2,10 @@ import { IDepTreeNode } from "../internal";
2
2
  export declare function getAtom(thing: any, property?: PropertyKey): IDepTreeNode;
3
3
  export declare function getAdministration(thing: any, property?: string): any;
4
4
  export declare function getDebugName(thing: any, property?: string): string;
5
+ /**
6
+ * Helper function for initializing observable structures, it applies:
7
+ * 1. allowStateChanges so we don't violate enforceActions.
8
+ * 2. untracked so we don't accidentaly subscribe to anything observable accessed during init in case the observable is created inside derivation.
9
+ * 3. batch to avoid state version updates
10
+ */
11
+ export declare function initObservable<T>(cb: () => T): T;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mobx",
3
- "version": "6.10.0",
3
+ "version": "6.10.2",
4
4
  "description": "Simple, scalable state management.",
5
5
  "source": "src/mobx.ts",
6
6
  "main": "dist/index.js",
@@ -2,8 +2,6 @@ import {
2
2
  CreateObservableOptions,
3
3
  isObservableMap,
4
4
  AnnotationsMap,
5
- startBatch,
6
- endBatch,
7
5
  asObservableObject,
8
6
  isPlainObject,
9
7
  ObservableObjectAdministration,
@@ -11,7 +9,8 @@ import {
11
9
  die,
12
10
  getOwnPropertyDescriptors,
13
11
  $mobx,
14
- ownKeys
12
+ ownKeys,
13
+ initObservable
15
14
  } from "../internal"
16
15
 
17
16
  export function extendObservable<A extends Object, B extends Object>(
@@ -40,9 +39,8 @@ export function extendObservable<A extends Object, B extends Object>(
40
39
  // Pull descriptors first, so we don't have to deal with props added by administration ($mobx)
41
40
  const descriptors = getOwnPropertyDescriptors(properties)
42
41
 
43
- const adm: ObservableObjectAdministration = asObservableObject(target, options)[$mobx]
44
- startBatch()
45
- try {
42
+ initObservable(() => {
43
+ const adm: ObservableObjectAdministration = asObservableObject(target, options)[$mobx]
46
44
  ownKeys(descriptors).forEach(key => {
47
45
  adm.extend_(
48
46
  key,
@@ -51,8 +49,7 @@ export function extendObservable<A extends Object, B extends Object>(
51
49
  !annotations ? true : key in annotations ? annotations[key] : true
52
50
  )
53
51
  })
54
- } finally {
55
- endBatch()
56
- }
52
+ })
53
+
57
54
  return target as any
58
55
  }
@@ -2,8 +2,6 @@ import {
2
2
  $mobx,
3
3
  asObservableObject,
4
4
  AnnotationsMap,
5
- endBatch,
6
- startBatch,
7
5
  CreateObservableOptions,
8
6
  ObservableObjectAdministration,
9
7
  collectStoredAnnotations,
@@ -13,7 +11,8 @@ import {
13
11
  ownKeys,
14
12
  extendObservable,
15
13
  addHiddenProp,
16
- storedAnnotationsSymbol
14
+ storedAnnotationsSymbol,
15
+ initObservable
17
16
  } from "../internal"
18
17
 
19
18
  // Hack based on https://github.com/Microsoft/TypeScript/issues/14829#issuecomment-322267089
@@ -30,9 +29,8 @@ export function makeObservable<T extends object, AdditionalKeys extends Property
30
29
  annotations?: AnnotationsMap<T, NoInfer<AdditionalKeys>>,
31
30
  options?: MakeObservableOptions
32
31
  ): T {
33
- const adm: ObservableObjectAdministration = asObservableObject(target, options)[$mobx]
34
- startBatch()
35
- try {
32
+ initObservable(() => {
33
+ const adm: ObservableObjectAdministration = asObservableObject(target, options)[$mobx]
36
34
  if (__DEV__ && annotations && target[storedAnnotationsSymbol]) {
37
35
  die(
38
36
  `makeObservable second arg must be nullish when using decorators. Mixing @decorator syntax with annotations is not supported.`
@@ -43,9 +41,7 @@ export function makeObservable<T extends object, AdditionalKeys extends Property
43
41
 
44
42
  // Annotate
45
43
  ownKeys(annotations).forEach(key => adm.make_(key, annotations![key]))
46
- } finally {
47
- endBatch()
48
- }
44
+ })
49
45
  return target
50
46
  }
51
47
 
@@ -72,20 +68,19 @@ export function makeAutoObservable<T extends object, AdditionalKeys extends Prop
72
68
  return extendObservable(target, target, overrides, options)
73
69
  }
74
70
 
75
- const adm: ObservableObjectAdministration = asObservableObject(target, options)[$mobx]
71
+ initObservable(() => {
72
+ const adm: ObservableObjectAdministration = asObservableObject(target, options)[$mobx]
76
73
 
77
- // Optimization: cache keys on proto
78
- // Assumes makeAutoObservable can be called only once per object and can't be used in subclass
79
- if (!target[keysSymbol]) {
80
- const proto = Object.getPrototypeOf(target)
81
- const keys = new Set([...ownKeys(target), ...ownKeys(proto)])
82
- keys.delete("constructor")
83
- keys.delete($mobx)
84
- addHiddenProp(proto, keysSymbol, keys)
85
- }
74
+ // Optimization: cache keys on proto
75
+ // Assumes makeAutoObservable can be called only once per object and can't be used in subclass
76
+ if (!target[keysSymbol]) {
77
+ const proto = Object.getPrototypeOf(target)
78
+ const keys = new Set([...ownKeys(target), ...ownKeys(proto)])
79
+ keys.delete("constructor")
80
+ keys.delete($mobx)
81
+ addHiddenProp(proto, keysSymbol, keys)
82
+ }
86
83
 
87
- startBatch()
88
- try {
89
84
  target[keysSymbol].forEach(key =>
90
85
  adm.make_(
91
86
  key,
@@ -93,8 +88,7 @@ export function makeAutoObservable<T extends object, AdditionalKeys extends Prop
93
88
  !overrides ? true : key in overrides ? overrides[key] : true
94
89
  )
95
90
  )
96
- } finally {
97
- endBatch()
98
- }
91
+ })
92
+
99
93
  return target
100
94
  }
@@ -29,7 +29,8 @@ import {
29
29
  assign,
30
30
  isStringish,
31
31
  createObservableAnnotation,
32
- createAutoAnnotation
32
+ createAutoAnnotation,
33
+ initObservable
33
34
  } from "../internal"
34
35
 
35
36
  export const OBSERVABLE = "observable"
@@ -211,12 +212,14 @@ const observableFactories: IObservableFactory = {
211
212
  decorators?: AnnotationsMap<T, never>,
212
213
  options?: CreateObservableOptions
213
214
  ): T {
214
- return extendObservable(
215
- globalState.useProxies === false || options?.proxy === false
216
- ? asObservableObject({}, options)
217
- : asDynamicObservableObject({}, options),
218
- props,
219
- decorators
215
+ return initObservable(() =>
216
+ extendObservable(
217
+ globalState.useProxies === false || options?.proxy === false
218
+ ? asObservableObject({}, options)
219
+ : asDynamicObservableObject({}, options),
220
+ props,
221
+ decorators
222
+ )
220
223
  )
221
224
  },
222
225
  ref: createDecoratorAnnotation(observableRefAnnotation),
package/src/core/atom.ts CHANGED
@@ -19,7 +19,7 @@ export const $mobx = Symbol("mobx administration")
19
19
 
20
20
  export interface IAtom extends IObservable {
21
21
  reportObserved(): boolean
22
- reportChanged()
22
+ reportChanged(): void
23
23
  }
24
24
 
25
25
  export class Atom implements IAtom {
@@ -27,6 +27,7 @@ export class Atom implements IAtom {
27
27
  isBeingObserved_ = false
28
28
  observers_ = new Set<IDerivation>()
29
29
 
30
+ batchId_: number
30
31
  diffValue_ = 0
31
32
  lastAccessedBy_ = 0
32
33
  lowestObserverState_ = IDerivationState_.NOT_TRACKING_
@@ -34,7 +35,9 @@ export class Atom implements IAtom {
34
35
  * Create a new atom. For debugging purposes it is recommended to give it a name.
35
36
  * The onBecomeObserved and onBecomeUnobserved callbacks can be used for resource management.
36
37
  */
37
- constructor(public name_ = __DEV__ ? "Atom@" + getNextId() : "Atom") {}
38
+ constructor(public name_ = __DEV__ ? "Atom@" + getNextId() : "Atom") {
39
+ this.batchId_ = globalState.inBatch ? globalState.batchId : NaN
40
+ }
38
41
 
39
42
  // onBecomeObservedListeners
40
43
  public onBOL: Set<Lambda> | undefined
@@ -65,14 +68,19 @@ export class Atom implements IAtom {
65
68
  * Invoke this method _after_ this method has changed to signal mobx that all its observers should invalidate.
66
69
  */
67
70
  public reportChanged() {
71
+ if (!globalState.inBatch || this.batchId_ !== globalState.batchId) {
72
+ // We could update state version only at the end of batch,
73
+ // but we would still have to switch some global flag here to signal a change.
74
+ globalState.stateVersion =
75
+ globalState.stateVersion < Number.MAX_SAFE_INTEGER
76
+ ? globalState.stateVersion + 1
77
+ : Number.MIN_SAFE_INTEGER
78
+ // Avoids the possibility of hitting the same globalState.batchId when it cycled through all integers (necessary?)
79
+ this.batchId_ = NaN
80
+ }
81
+
68
82
  startBatch()
69
83
  propagateChanged(this)
70
- // We could update state version only at the end of batch,
71
- // but we would still have to switch some global flag here to signal a change.
72
- globalState.stateVersion =
73
- globalState.stateVersion < Number.MAX_SAFE_INTEGER
74
- ? globalState.stateVersion + 1
75
- : Number.MIN_SAFE_INTEGER
76
84
  endBatch()
77
85
  }
78
86
 
@@ -63,6 +63,12 @@ export class MobXGlobals {
63
63
  */
64
64
  inBatch: number = 0
65
65
 
66
+ /**
67
+ * ID of the latest batch. Used to suppress reportChanged of newly created atoms.
68
+ * Note the value persists even after batch ended.
69
+ */
70
+ batchId: number = Number.MIN_SAFE_INTEGER
71
+
66
72
  /**
67
73
  * Observables that don't have observers anymore, and are about to be
68
74
  * suspended, unless somebody else accesses it in the same batch
@@ -104,6 +104,12 @@ export function queueForUnobservation(observable: IObservable) {
104
104
  * Avoids unnecessary recalculations.
105
105
  */
106
106
  export function startBatch() {
107
+ if (globalState.inBatch === 0) {
108
+ globalState.batchId =
109
+ globalState.batchId < Number.MAX_SAFE_INTEGER
110
+ ? globalState.batchId + 1
111
+ : Number.MIN_SAFE_INTEGER
112
+ }
107
113
  globalState.inBatch++
108
114
  }
109
115
 
@@ -1,8 +1,6 @@
1
1
  import {
2
2
  getNextId,
3
3
  addHiddenFinalProp,
4
- allowStateChangesStart,
5
- allowStateChangesEnd,
6
4
  makeIterable,
7
5
  addHiddenProp,
8
6
  ObservableArrayAdministration,
@@ -11,7 +9,8 @@ import {
11
9
  IEnhancer,
12
10
  isObservableArray,
13
11
  IObservableArray,
14
- defineProperty
12
+ defineProperty,
13
+ initObservable
15
14
  } from "../internal"
16
15
 
17
16
  // Bug in safari 9.* (or iOS 9 safari mobile). See #364
@@ -61,23 +60,22 @@ class LegacyObservableArray<T> extends StubArray {
61
60
  owned = false
62
61
  ) {
63
62
  super()
63
+ initObservable(() => {
64
+ const adm = new ObservableArrayAdministration(name, enhancer, owned, true)
65
+ adm.proxy_ = this as any
66
+ addHiddenFinalProp(this, $mobx, adm)
67
+
68
+ if (initialValues && initialValues.length) {
69
+ // @ts-ignore
70
+ this.spliceWithArray(0, 0, initialValues)
71
+ }
64
72
 
65
- const adm = new ObservableArrayAdministration(name, enhancer, owned, true)
66
- adm.proxy_ = this as any
67
- addHiddenFinalProp(this, $mobx, adm)
68
-
69
- if (initialValues && initialValues.length) {
70
- const prev = allowStateChangesStart(true)
71
- // @ts-ignore
72
- this.spliceWithArray(0, 0, initialValues)
73
- allowStateChangesEnd(prev)
74
- }
75
-
76
- if (safariPrototypeSetterInheritanceBug) {
77
- // Seems that Safari won't use numeric prototype setter untill any * numeric property is
78
- // defined on the instance. After that it works fine, even if this property is deleted.
79
- Object.defineProperty(this, "0", ENTRY_0)
80
- }
73
+ if (safariPrototypeSetterInheritanceBug) {
74
+ // Seems that Safari won't use numeric prototype setter untill any * numeric property is
75
+ // defined on the instance. After that it works fine, even if this property is deleted.
76
+ Object.defineProperty(this, "0", ENTRY_0)
77
+ }
78
+ })
81
79
  }
82
80
 
83
81
  concat(...arrays: T[][]): T[] {
@@ -22,13 +22,12 @@ import {
22
22
  registerListener,
23
23
  spyReportEnd,
24
24
  spyReportStart,
25
- allowStateChangesStart,
26
- allowStateChangesEnd,
27
25
  assertProxies,
28
26
  reserveArrayBuffer,
29
27
  hasProp,
30
28
  die,
31
- globalState
29
+ globalState,
30
+ initObservable
32
31
  } from "../internal"
33
32
 
34
33
  const SPLICE = "splice"
@@ -406,16 +405,16 @@ export function createObservableArray<T>(
406
405
  owned = false
407
406
  ): IObservableArray<T> {
408
407
  assertProxies()
409
- const adm = new ObservableArrayAdministration(name, enhancer, owned, false)
410
- addHiddenFinalProp(adm.values_, $mobx, adm)
411
- const proxy = new Proxy(adm.values_, arrayTraps) as any
412
- adm.proxy_ = proxy
413
- if (initialValues && initialValues.length) {
414
- const prev = allowStateChangesStart(true)
415
- adm.spliceWithArray_(0, 0, initialValues)
416
- allowStateChangesEnd(prev)
417
- }
418
- return proxy
408
+ return initObservable(() => {
409
+ const adm = new ObservableArrayAdministration(name, enhancer, owned, false)
410
+ addHiddenFinalProp(adm.values_, $mobx, adm)
411
+ const proxy = new Proxy(adm.values_, arrayTraps) as any
412
+ adm.proxy_ = proxy
413
+ if (initialValues && initialValues.length) {
414
+ adm.spliceWithArray_(0, 0, initialValues)
415
+ }
416
+ return proxy
417
+ })
419
418
  }
420
419
 
421
420
  // eslint-disable-next-line
@@ -35,7 +35,7 @@ import {
35
35
  UPDATE,
36
36
  IAtom,
37
37
  PureSpyEvent,
38
- allowStateChanges
38
+ initObservable
39
39
  } from "../internal"
40
40
 
41
41
  export interface IKeyValueMap<V = any> {
@@ -90,11 +90,12 @@ export type IObservableMapInitialValues<K = any, V = any> =
90
90
  // just extend Map? See also https://gist.github.com/nestharus/13b4d74f2ef4a2f4357dbd3fc23c1e54
91
91
  // But: https://github.com/mobxjs/mobx/issues/1556
92
92
  export class ObservableMap<K = any, V = any>
93
- implements Map<K, V>, IInterceptable<IMapWillChange<K, V>>, IListenable {
93
+ implements Map<K, V>, IInterceptable<IMapWillChange<K, V>>, IListenable
94
+ {
94
95
  [$mobx] = ObservableMapMarker
95
- data_: Map<K, ObservableValue<V>>
96
- hasMap_: Map<K, ObservableValue<boolean>> // hasMap, not hashMap >-).
97
- keysAtom_: IAtom
96
+ data_!: Map<K, ObservableValue<V>>
97
+ hasMap_!: Map<K, ObservableValue<boolean>> // hasMap, not hashMap >-).
98
+ keysAtom_!: IAtom
98
99
  interceptors_
99
100
  changeListeners_
100
101
  dehancer: any
@@ -107,11 +108,13 @@ export class ObservableMap<K = any, V = any>
107
108
  if (!isFunction(Map)) {
108
109
  die(18)
109
110
  }
110
- this.keysAtom_ = createAtom(__DEV__ ? `${this.name_}.keys()` : "ObservableMap.keys()")
111
- this.data_ = new Map()
112
- this.hasMap_ = new Map()
113
- allowStateChanges(true, () => {
114
- this.merge(initialData)
111
+ initObservable(() => {
112
+ this.keysAtom_ = createAtom(__DEV__ ? `${this.name_}.keys()` : "ObservableMap.keys()")
113
+ this.data_ = new Map()
114
+ this.hasMap_ = new Map()
115
+ if (initialData) {
116
+ this.merge(initialData)
117
+ }
115
118
  })
116
119
  }
117
120
 
@@ -46,7 +46,8 @@ import {
46
46
  getAdministration,
47
47
  getDebugName,
48
48
  objectPrototype,
49
- MakeResult
49
+ MakeResult,
50
+ checkIfStateModificationsAreAllowed
50
51
  } from "../internal"
51
52
 
52
53
  const descriptorCache = Object.create(null)
@@ -314,6 +315,7 @@ export class ObservableObjectAdministration
314
315
  descriptor: PropertyDescriptor,
315
316
  proxyTrap: boolean = false
316
317
  ): boolean | null {
318
+ checkIfStateModificationsAreAllowed(this.keysAtom_)
317
319
  try {
318
320
  startBatch()
319
321
 
@@ -368,6 +370,7 @@ export class ObservableObjectAdministration
368
370
  enhancer: IEnhancer<any>,
369
371
  proxyTrap: boolean = false
370
372
  ): boolean | null {
373
+ checkIfStateModificationsAreAllowed(this.keysAtom_)
371
374
  try {
372
375
  startBatch()
373
376
 
@@ -432,6 +435,7 @@ export class ObservableObjectAdministration
432
435
  options: IComputedValueOptions<any>,
433
436
  proxyTrap: boolean = false
434
437
  ): boolean | null {
438
+ checkIfStateModificationsAreAllowed(this.keysAtom_)
435
439
  try {
436
440
  startBatch()
437
441
 
@@ -490,6 +494,7 @@ export class ObservableObjectAdministration
490
494
  * @returns {boolean|null} true on success, false on failure (proxyTrap + non-configurable), null when cancelled by interceptor
491
495
  */
492
496
  delete_(key: PropertyKey, proxyTrap: boolean = false): boolean | null {
497
+ checkIfStateModificationsAreAllowed(this.keysAtom_)
493
498
  // No such prop
494
499
  if (!hasProp(this.target_, key)) {
495
500
  return true
@@ -27,7 +27,8 @@ import {
27
27
  DELETE,
28
28
  ADD,
29
29
  die,
30
- isFunction
30
+ isFunction,
31
+ initObservable
31
32
  } from "../internal"
32
33
 
33
34
  const ObservableSetMarker = {}
@@ -65,7 +66,7 @@ export type ISetWillChange<T = any> =
65
66
  export class ObservableSet<T = any> implements Set<T>, IInterceptable<ISetWillChange>, IListenable {
66
67
  [$mobx] = ObservableSetMarker
67
68
  private data_: Set<any> = new Set()
68
- atom_: IAtom
69
+ atom_!: IAtom
69
70
  changeListeners_
70
71
  interceptors_
71
72
  dehancer: any
@@ -79,11 +80,13 @@ export class ObservableSet<T = any> implements Set<T>, IInterceptable<ISetWillCh
79
80
  if (!isFunction(Set)) {
80
81
  die(22)
81
82
  }
82
- this.atom_ = createAtom(this.name_)
83
83
  this.enhancer_ = (newV, oldV) => enhancer(newV, oldV, name_)
84
- if (initialData) {
85
- this.replace(initialData)
86
- }
84
+ initObservable(() => {
85
+ this.atom_ = createAtom(this.name_)
86
+ if (initialData) {
87
+ this.replace(initialData)
88
+ }
89
+ })
87
90
  }
88
91
 
89
92
  private dehanceValue_<X extends T | undefined>(value: X): X {
@@ -10,7 +10,13 @@ import {
10
10
  isReaction,
11
11
  isObservableSet,
12
12
  die,
13
- isFunction
13
+ isFunction,
14
+ allowStateChangesStart,
15
+ untrackedStart,
16
+ allowStateChangesEnd,
17
+ untrackedEnd,
18
+ startBatch,
19
+ endBatch
14
20
  } from "../internal"
15
21
 
16
22
  export function getAtom(thing: any, property?: PropertyKey): IDepTreeNode {
@@ -92,3 +98,22 @@ export function getDebugName(thing: any, property?: string): string {
92
98
  }
93
99
  return named.name_
94
100
  }
101
+
102
+ /**
103
+ * Helper function for initializing observable structures, it applies:
104
+ * 1. allowStateChanges so we don't violate enforceActions.
105
+ * 2. untracked so we don't accidentaly subscribe to anything observable accessed during init in case the observable is created inside derivation.
106
+ * 3. batch to avoid state version updates
107
+ */
108
+ export function initObservable<T>(cb: () => T): T {
109
+ const derivation = untrackedStart()
110
+ const allowStateChanges = allowStateChangesStart(true)
111
+ startBatch()
112
+ try {
113
+ return cb()
114
+ } finally {
115
+ endBatch()
116
+ allowStateChangesEnd(allowStateChanges)
117
+ untrackedEnd(derivation)
118
+ }
119
+ }