mobx 7.0.4 → 7.0.6

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.
@@ -1,3 +1,4 @@
1
- import { Annotation } from "../internal";
1
+ import { Annotation, CreateObservableOptions } from "../internal";
2
2
  export declare const autoAnnotation: Annotation;
3
3
  export declare function createAutoAnnotation(options?: object): Annotation;
4
+ export declare function getAutoAnnotation(options: CreateObservableOptions): Annotation;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mobx",
3
- "version": "7.0.4",
3
+ "version": "7.0.6",
4
4
  "description": "Simple, scalable state management.",
5
5
  "source": "src/mobx.ts",
6
6
  "type": "commonjs",
@@ -26,7 +26,7 @@ import {
26
26
  AnnotationsMap,
27
27
  assign,
28
28
  createObservableAnnotation,
29
- createAutoAnnotation,
29
+ getAutoAnnotation,
30
30
  initObservable,
31
31
  decorateObservable20223_
32
32
  } from "../internal"
@@ -87,7 +87,7 @@ export function getEnhancerFromOptions(options: CreateObservableOptions): IEnhan
87
87
  export function getAnnotationFromOptions(
88
88
  options?: CreateObservableOptions
89
89
  ): Annotation | undefined {
90
- return options ? options.defaultDecorator ?? createAutoAnnotation(options) : undefined
90
+ return options ? options.defaultDecorator ?? getAutoAnnotation(options) : undefined
91
91
  }
92
92
 
93
93
  export function getEnhancerFromAnnotation(annotation?: Annotation): IEnhancer<any> {
@@ -117,34 +117,42 @@ export function endBatch() {
117
117
  // the nested call instead of recursing; the outer loop re-reads list.length on
118
118
  // every iteration, so it picks up anything the nested dispose() pushes onto the
119
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
- }
120
+ if (!globalState.isRunningUnobservations && globalState.pendingUnobservations.length > 0) {
121
+ runPendingUnobservations()
122
+ }
123
+ }
124
+ }
125
+
126
+ // Only called when there is something to unobserve, so the common endBatch() skips the
127
+ // try/finally and the pendingUnobservations reallocation entirely
128
+ function runPendingUnobservations() {
129
+ globalState.isRunningUnobservations = true
130
+ try {
131
+ const list = globalState.pendingUnobservations
132
+ for (let i = 0; i < list.length; i++) {
133
+ const observable = list[i]
134
+ observable.isPendingUnobservation = false
135
+ if (!observable.observers_ || observable.observers_.size === 0) {
136
+ // release the empty Set so unobserved atoms don't keep paying for it
137
+ observable.observers_ = null
138
+ if (observable.isBeingObserved) {
139
+ // if this observable had reactive observers, trigger the hooks
140
+ observable.isBeingObserved = false
141
+ observable.onBUO()
142
+ }
143
+ if (observable instanceof ComputedValue) {
144
+ // computed values are automatically teared down when the last observer leaves
145
+ // this process happens recursively, this computed might be the last observabe of another, etc..
146
+ observable.suspend_()
139
147
  }
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
146
148
  }
147
149
  }
150
+ globalState.pendingUnobservations = []
151
+ } finally {
152
+ // Always release the guard, even if an onBUO handler (user code) threw,
153
+ // otherwise every future endBatch() would see isRunningUnobservations
154
+ // stuck true and silently stop draining pendingUnobservations forever.
155
+ globalState.isRunningUnobservations = false
148
156
  }
149
157
  }
150
158
 
@@ -66,6 +66,7 @@ export class Reaction implements IDerivation, IReactionPublic {
66
66
  unboundDepsCount_ = 0
67
67
 
68
68
  private flags_ = 0b00000
69
+ private removeAbortListener_: Lambda | undefined = undefined
69
70
 
70
71
  constructor(
71
72
  public name_: string = __DEV__ ? "Reaction@" + getNextId() : "Reaction",
@@ -221,6 +222,8 @@ export class Reaction implements IDerivation, IReactionPublic {
221
222
  dispose() {
222
223
  if (!this.isDisposed) {
223
224
  this.isDisposed = true
225
+ // also covers self-disposal (`r.dispose()`, `when`), which bypasses the disposer
226
+ this.removeAbortListener_?.()
224
227
  if (!this.isRunning) {
225
228
  // if disposed while running, clean up later. Maybe not optimal, but rare case
226
229
  startBatch()
@@ -231,11 +234,11 @@ export class Reaction implements IDerivation, IReactionPublic {
231
234
  }
232
235
 
233
236
  getDisposer_(abortSignal?: GenericAbortSignal): IReactionDisposer {
234
- const dispose = (() => {
235
- this.dispose()
236
- abortSignal?.removeEventListener?.("abort", dispose)
237
- }) as IReactionDisposer
238
- abortSignal?.addEventListener?.("abort", dispose)
237
+ const dispose = (() => this.dispose()) as IReactionDisposer
238
+ if (abortSignal?.addEventListener && !this.isDisposed) {
239
+ abortSignal.addEventListener("abort", dispose)
240
+ this.removeAbortListener_ = () => abortSignal.removeEventListener?.("abort", dispose)
241
+ }
239
242
  dispose[$mobx] = this
240
243
 
241
244
  if ("dispose" in Symbol && typeof Symbol.dispose === "symbol") {
@@ -13,7 +13,8 @@ import {
13
13
  autoActionBound,
14
14
  isGenerator,
15
15
  MakeResult,
16
- isAction
16
+ isAction,
17
+ CreateObservableOptions
17
18
  } from "../internal"
18
19
 
19
20
  const AUTO = "true"
@@ -29,6 +30,17 @@ export function createAutoAnnotation(options?: object): Annotation {
29
30
  }
30
31
  }
31
32
 
33
+ // The auto annotation only depends on `deep` and `autoBind`, so share one instance per combination
34
+ // instead of allocating one for every object created with options
35
+ const autoAnnotations: Annotation[] = [autoAnnotation]
36
+
37
+ export function getAutoAnnotation(options: CreateObservableOptions): Annotation {
38
+ const deep = options.deep !== false
39
+ const autoBind = !!options.autoBind
40
+ const idx = (deep ? 0 : 1) | (autoBind ? 2 : 0)
41
+ return (autoAnnotations[idx] ??= createAutoAnnotation({ deep, autoBind }))
42
+ }
43
+
32
44
  function make_(
33
45
  adm: ObservableObjectAdministration,
34
46
  key: PropertyKey,
@@ -14,8 +14,13 @@ export function registerInterceptor<T>(
14
14
  interceptable: IInterceptable<T>,
15
15
  handler: IInterceptor<T>
16
16
  ): Lambda {
17
- const interceptors = interceptable.interceptors_ || (interceptable.interceptors_ = [])
18
- interceptors.push(handler)
17
+ let interceptors = interceptable.interceptors_
18
+ if (interceptors) {
19
+ interceptors.push(handler)
20
+ } else {
21
+ // Allocate at the exact size: pushing onto an empty array over-allocates its backing store
22
+ interceptors = interceptable.interceptors_ = [handler]
23
+ }
19
24
  return once(() => {
20
25
  const idx = interceptors.indexOf(handler)
21
26
  if (idx !== -1) {
@@ -9,8 +9,13 @@ export function hasListeners(listenable: IListenable) {
9
9
  }
10
10
 
11
11
  export function registerListener(listenable: IListenable, handler: Function): Lambda {
12
- const listeners = listenable.changeListeners_ || (listenable.changeListeners_ = [])
13
- listeners.push(handler)
12
+ let listeners = listenable.changeListeners_
13
+ if (listeners) {
14
+ listeners.push(handler)
15
+ } else {
16
+ // Allocate at the exact size: pushing onto an empty array over-allocates its backing store
17
+ listeners = listenable.changeListeners_ = [handler]
18
+ }
14
19
  return once(() => {
15
20
  const idx = listeners.indexOf(handler)
16
21
  if (idx !== -1) {
@@ -95,8 +95,7 @@ const arrayTraps = {
95
95
  const adm: ObservableArrayAdministration = target[$mobx]
96
96
  if (name === "length") {
97
97
  adm.setArrayLength_(value)
98
- }
99
- if (typeof name === "symbol" || isNaN(name)) {
98
+ } else if (typeof name === "symbol" || isNaN(name)) {
100
99
  target[name] = value
101
100
  } else {
102
101
  // numeric string
@@ -151,7 +150,7 @@ export class ObservableArrayAdministration
151
150
  }
152
151
 
153
152
  setArrayLength_(newLength: number) {
154
- if (typeof newLength !== "number" || isNaN(newLength) || newLength < 0) {
153
+ if (!Number.isInteger(newLength) || newLength < 0) {
155
154
  die(40, newLength)
156
155
  }
157
156
  let currentLength = this.values_.length
@@ -127,7 +127,8 @@ export class ObservableSet<T = any> implements Set<T>, IInterceptable<ISetWillCh
127
127
  }
128
128
  if (!this.has(value)) {
129
129
  transaction(() => {
130
- this.data_.add(this.enhancer_(value, undefined))
130
+ value = this.enhancer_(value, undefined) // report the stored value, like map and array
131
+ this.data_.add(value)
131
132
  this.atom_.reportChanged()
132
133
  })
133
134
  const notifySpy = __DEV__ && isSpyEnabled()
@@ -79,7 +79,7 @@ export class ObservableValue<T>
79
79
  object: this,
80
80
  observableKind: "value",
81
81
  debugObjectName: this.name_,
82
- newValue: "" + this.value_?.toString()
82
+ newValue: this.value_
83
83
  })
84
84
  }
85
85
  }