mobx 7.0.3 → 7.0.5

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.3",
3
+ "version": "7.0.5",
4
4
  "description": "Simple, scalable state management.",
5
5
  "source": "src/mobx.ts",
6
6
  "type": "commonjs",
@@ -19,13 +19,6 @@ import {
19
19
  recordAnnotationApplied
20
20
  } from "../internal"
21
21
 
22
- // Hack based on https://github.com/Microsoft/TypeScript/issues/14829#issuecomment-322267089
23
- // We need this, because otherwise, AdditionalKeys is going to be inferred to be any
24
- // set of superfluous keys. But, we rather want to get a compile error unless AdditionalKeys is
25
- // _explicity_ passed as generic argument
26
- // Fixes: https://github.com/mobxjs/mobx/issues/2325#issuecomment-691070022
27
- type NoInfer<T> = [T][T extends any ? 0 : never]
28
-
29
22
  export function makeObservable<T extends object, AdditionalKeys extends PropertyKey = never>(
30
23
  target: T,
31
24
  annotations: AnnotationsMap<T, NoInfer<AdditionalKeys>>,
@@ -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> {
@@ -216,7 +216,10 @@ export class ComputedValue<T> implements IObservable, IComputedValue<T>, IDeriva
216
216
  reportObserved(this)
217
217
  if (shouldCompute(this)) {
218
218
  let prevTrackingContext = globalState.trackingContext
219
- if (this.keepAlive_ && !prevTrackingContext) {
219
+ if (!prevTrackingContext && (this.keepAlive_ || this.isBeingObserved)) {
220
+ // An observed or keep-alive computed can be recomputed by an untracked
221
+ // read, for example from inside an action. Its dependencies are still
222
+ // transitively observed and must fire their lifecycle hooks
220
223
  globalState.trackingContext = this
221
224
  }
222
225
  if (this.trackAndCompute()) {
@@ -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
 
@@ -45,7 +45,7 @@ export interface IReactionPublic {
45
45
  dispose(): void
46
46
  }
47
47
 
48
- export interface IReactionDisposer {
48
+ export interface IReactionDisposer extends Disposable {
49
49
  (): void
50
50
  [$mobx]: Reaction
51
51
  }
@@ -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) {