From 96e95f80bd6afaf09184c124801d0e016c71db73 Mon Sep 17 00:00:00 2001 From: Michel Weststrate Date: Thu, 30 Jul 2026 22:05:28 +0200 Subject: [PATCH 01/10] docs: add plan for removing legacy MobX APIs --- API_REMOVAL_PLAN.md | 289 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 289 insertions(+) create mode 100644 API_REMOVAL_PLAN.md diff --git a/API_REMOVAL_PLAN.md b/API_REMOVAL_PLAN.md new file mode 100644 index 000000000..08a534b3d --- /dev/null +++ b/API_REMOVAL_PLAN.md @@ -0,0 +1,289 @@ +# Plan: Remove legacy APIs from `packages/mobx` + +**Goal**: Remove a set of public MobX APIs and every implementation detail that exists only to support them, leaving the remaining public surface and internal reactivity intact. + +**Branch**: `mobx8-cleanup` (already checked out). One commit per feature area for reviewability. + +**Tech stack**: TypeScript, Rollup build, Jest tests (`jest --config jest.projects.js`), `tsc --noEmit` for types. + +## APIs being removed + +Object API (top-level exports only — instance methods on Observable Map/Set/Array are KEPT): +`get`, `has`, `set`, `remove`, `keys`, `values`, `entries`, `ownKeys` + +Introspection: `getDebugName`, `getDependencyTree`, `getObserverTree` + +Interception / observation: `intercept`, `observe`, `_interceptReads`, `spy` + +Concept: the entire `dehancer` mechanism. + +## Decisions (confirmed with the requester) + +1. **Object-API scope**: remove only the standalone `mobx.get/set/has/remove/keys/values/entries/ownKeys` functions in `src/api/object-api.ts`. KEEP `map.get()/set()/has()/keys()/values()/entries()`, `set.has()`, `array.remove()` — these are part of the Map/Set/Array contract and used by the proxy traps / internals. In particular the administration methods `keys_`, `set_`, `delete_`, `has_`, `get_`, `ownKeys_` on `ObservableObjectAdministration` STAY (used by `dynamicobject.ts` proxy traps). +2. **Docs**: delete the now-empty topic pages and prune all references (see per-commit doc steps + the final doc sweep). +3. **Downstream**: update the single dependent spot, `packages/mobx-react-lite/src/useObserver.ts` (uses `getDependencyTree`), so the monorepo still builds. No other package changes. + +## What is KEPT (do NOT remove — shared/core machinery) + +- `IEnhancer` and all enhancer functions (`deepEnhancer`, `shallowEnhancer`, `referenceEnhancer`, `refStructEnhancer`) and per-admin `enhancer_` fields — the enhancer concept is core observable creation, distinct from the dehancer. +- `getAtom`, `getAdministration`, `getObservers`, `hasObservers`, `IDepTreeNode`, `IObservable` — broad core infra. +- `defineProperty` / `apiDefineProperty` (lives in `object-api.ts`) and error code `39`. +- `IMapEntry`, `IMapEntries`, `IKeyValueMap` — general map types, not change events. +- `untrackedStart/End`, batch helpers, mutation constants (`ADD/UPDATE/DELETE/REMOVE/SPLICE/CREATE`). + +## Verification (run after every commit, from `packages/mobx`) + +```bash +cd packages/mobx +yarn jest --config jest.projects.js # or: npx jest --config jest.projects.js +npx tsc --noEmit # type check +npx eslint src/**/* # lint (catches unused imports) +``` + +For commit 6 also build the whole monorepo / mobx-react-lite so the downstream fix is validated. + +When removing an error code, first confirm no other caller: `grep -rn "die()" packages/mobx/src`. + +`base/api.js` (`__tests__/base/api.js`) asserts the exact set of `Object.keys(mobx)`. Every export removed below must also be deleted from its expected array — do this in the same commit or the whole suite fails. + +--- + +## Dependency / execution order + +| Commit | Feature | Notes | +| ------ | ----------------------------------- | ------------------------------------------------------------------------------------------------------------------ | +| 1 | `spy` | Most cross-cutting (action/reaction/computed + all observable types). Removes devtools hook's `spy` ref. | +| 2 | `intercept` + interceptor machinery | `*WillChange` types, `interceptChange`, error 14. | +| 3 | `_interceptReads` + `dehancer` | `_interceptReads` is the only writer of `dehancer`; remove together. Removes `raw()`. | +| 4 | `observe` + listener machinery | `*DidChange` types (now unused after spy+observe gone). | +| 5 | Object API | `get/has/set/remove/keys/values/entries/ownKeys`, errors 5–11 & 38. | +| 6 | Introspection | `getDebugName/getDependencyTree/getObserverTree` + mobx-react-lite fix + devtools hook removal + final docs sweep. | + +Each commit is independently green. Commits 1–5 are largely independent; do them in this order to keep type-only cleanups (change-event interfaces) clean. + +--- + +## Commit 1 — Remove `spy` + +**Delete** + +- `src/core/spy.ts` entirely (`isSpyEnabled`, `spyReport`, `spyReportStart`, `spyReportEnd`, `END_EVENT`, `PureSpyEvent`, `SpyEvent`, `spy`). +- `internal.ts:30` (`export * from "./core/spy"`). +- `mobx.ts:39` (`spy` export) and the whole **Devtools hook** block (`mobx.ts:158-167`) — it is spy-centric; this also drops its `getDebugName` reference (removed in commit 6) and the `spy` import at `mobx.ts:28`. +- `globalState.spyListeners` field (`core/globalstate.ts:106`) and its entry in `persistentKeys` (`core/globalstate.ts:11`). +- Spy-only change type `IComputedDidChange` (`core/computedvalue.ts:47-54`) — verify no remaining use (`observe` builds its computed-change object inline, so it does not need the type; confirm before deleting). +- `IBoxDidChange` (`observablevalue.ts:39-47`) — spy-only, not exported. + +**Strip `spyReport*` / `isSpyEnabled` call sites and their imports** in: + +- `core/action.ts` (`_startAction` ~97-108, `_endAction` ~154-156, imports ~5-8) +- `core/reaction.ts` (`runReaction_` ~138-144, `track` ~160-168 & ~183-187, `reportExceptionInDerivation_` ~209-216, imports ~14-17) +- `core/computedvalue.ts` (`trackAndCompute` ~265-274, imports) +- `types/observablevalue.ts` (constructor CREATE ~75-84, `set` ~98-112, imports) +- `types/observablearray.ts` (`notifyArrayChildUpdate_` ~266-275, `notifyArraySplice_` ~296-306, imports) +- `types/observablemap.ts` (`delete`/`updateValue_`/`addValue_`, imports) +- `types/observableset.ts` (`add`/`delete`, imports) +- `types/observableobject.ts` (`setObservablePropValue_`, `delete_`, `notifyPropertyAddition_`, imports) + +Keep the `notifyListeners(...)` calls that sit alongside the removed `spyReport(...)` calls — those belong to `observe` (removed in commit 4). Only remove the spy half here. + +**Tests** + +- Delete `__tests__/base/spy.js`. +- `__tests__/base/extras.js`: remove the `"spy 1"` test (~143-162). +- Remove `mobx.spy` usage (and any resulting empty assertions) from: `base/observables.js` (~893, 934, 985), `base/action.js` (~311, 471), `base/flow.js` (~162), `base/typescript-tests.ts` (~532-685), `base/babel-tests.js` (~324-491), `base/stage3-decorators.ts` (~380-524). Where spy is only a logging probe inside a broader test, drop the spy scaffolding but keep the rest of the test (per "keep minimal"). +- `base/api.js`: remove `"spy"` from the expected exports array. +- Regenerate/prune affected snapshots under `__tests__/base/__snapshots__/` (`spy`, `observables`, `flow`, decorator files). + +**Docs** + +- `docs/analyzing-reactivity.md`: remove the `spy` section (~48-84). +- `docs/intercept-and-observe.md`: remove the `spy` row in the event-overview table (~150). +- `docs/api.md`: remove the `spy` entry (~465-470). + +**Commit**: `git commit -am "refactor(mobx): remove spy API and spy reporting machinery"` + +--- + +## Commit 2 — Remove `intercept` + interceptor machinery + +**Delete** + +- `src/api/intercept.ts` entirely; `internal.ts:39`; `mobx.ts:94` (`intercept`). +- `src/types/intercept-utils.ts` entirely (`IInterceptor`, `IInterceptable`, `hasInterceptors`, `registerInterceptor`, `interceptChange`); `internal.ts:48`; `mobx.ts:47` (`IInterceptable`), `mobx.ts:48` (`IInterceptor`). +- Error code `14` (`errors.ts:26`). + +**Strip per-class interceptor support** (field `interceptors_`, `implements IInterceptable`, and each `hasInterceptors(this)`/`interceptChange(...)` block): + +- `types/observablevalue.ts` (field ~61, `prepareNewValue_` ~118-128) +- `types/observablearray.ts` (field ~117, `spliceWithArray_` ~199-212, `set_` ~320-331) +- `types/observablemap.ts` (field ~94, `set` ~139-150, `delete` ~161-170) +- `types/observableset.ts` (field ~67, `add` ~115-127, `delete` ~160-169) +- `types/observableobject.ts` (field ~91, `setObservablePropValue_` ~166-177, `defineProperty_` ~322-338, `defineObservableProperty_` ~376-387, `defineComputedProperty_` ~441-451, `delete_` ~495-505) + +**Remove `*WillChange` change-event types** (used only by interceptors) and their `mobx.ts` exports: + +- `IValueWillChange` (`observablevalue.ts`; `mobx.ts:54`) +- `IArrayWillChange`, `IArrayWillSplice` (`observablearray.ts`; `mobx.ts:58,59`) +- `IMapWillChange` (`observablemap.ts`; `mobx.ts:68`) +- `ISetWillChange`, `ISetWillAddChange`, `ISetWillDeleteChange` (`observableset.ts`; `mobx.ts:75`) +- `IObjectWillChange` (`observableobject.ts`; `mobx.ts:71`) + +**Tests** + +- Delete `__tests__/base/intercept.js`. +- `base/object-api.js`: remove the intercept portions of the `observe & intercept` tests (~423-477); keep any observe-only assertions until commit 4. +- `base/map.js` (~1249, 1308), `base/set.js` (~478-514): remove `intercept`-based sub-tests, keeping surrounding map/set behavior tests. +- `base/typescript-tests.ts` (~2151-2261): remove intercept type-inference tests. +- `base/api.js`: remove `"intercept"` from expected exports. + +**Docs** + +- `docs/intercept-and-observe.md`: remove the `intercept` sections (~15, 18-68). (Page fully deleted in commit 4.) +- `docs/api.md`: remove the `intercept` entry (~281-286). + +**Commit**: `git commit -am "refactor(mobx): remove intercept API and interceptor machinery"` + +--- + +## Commit 3 — Remove `_interceptReads` + the `dehancer` concept + +**Delete** + +- `src/api/intercept-read.ts` entirely (`interceptReads`, `ReadInterceptor`); `internal.ts:38`; `mobx.ts:141` (`interceptReads as _interceptReads`). + +**Strip per-admin `dehancer` field + dehance methods and every call site**: + +- `types/observablevalue.ts`: field `dehancer` (~64), `dehanceValue` (~87-92) and its call in `get()` (~150); also remove `raw()` (~153-156) — it exists only to return the un-dehanced value. +- `types/observablearray.ts`: field (~120), `dehanceValue_` (~134-139), `dehanceValues_` (~141-146), and calls in `get_` (~311), `spliceWithArray_` (~225), the `remove` extension (~454), and read helpers `simpleFunc` (~508), `mapLikeFunc` (~518), `reduceLikeFunc` (~530). Each becomes a plain pass-through of the raw value(s). +- `types/observablemap.ts`: field `dehancer` (~96), `dehanceValue_` (~297-302), calls in `get` (~278, 280). +- `types/observableset.ts`: field `dehancer` (~68), `dehanceValue_` (~85-90), calls in `has` (~204), `values` (~229). +- (`ObservableObjectAdministration` has no dehancer field — nothing to do there.) + +Do NOT touch `enhancer_` fields or `IEnhancer` — those are core. + +**Tests** + +- `__tests__/base/array.js`: remove the `"dehances last value on shift/pop"` test (~538-552) and the entire `describe("dehances")` block (~698-880). These are the only dehancer tests and use `mobx._getAdministration(array).dehancer` directly. +- `base/api.js`: remove `"_interceptReads"` from expected exports. + +**Docs**: none (`_interceptReads`/`dehancer` are undocumented). + +**Commit**: `git commit -am "refactor(mobx): remove _interceptReads and the dehancer concept"` + +--- + +## Commit 4 — Remove `observe` + listener machinery + +**Delete** + +- `src/api/observe.ts` entirely; `internal.ts:43`; `mobx.ts:93` (`observe`). +- `src/types/listen-utils.ts` entirely (`IListenable`, `hasListeners`, `registerListener`, `notifyListeners`); `internal.ts:49`; `mobx.ts:49` (`IListenable`). + +**Strip per-class listener support** (field `changeListeners_`, `implements IListenable`, and each `hasListeners`/`notifyListeners` block): + +- `types/observablevalue.ts` (field ~62, `setNewValue_` ~138-145) +- `types/observablearray.ts` (field ~118, `notifyArrayChildUpdate_` ~250-271, `notifyArraySplice_` ~279-303) — after removing both spy (commit 1) and listeners, simplify these notify methods to just the atom `reportChanged()`/core mutation they still need. +- `types/observablemap.ts` (field ~95, `delete` ~173-198, `updateValue_` ~212-231, `addValue_` ~253-269) +- `types/observableset.ts` (field ~66, `add` ~134-150, `delete` ~172-193) +- `types/observableobject.ts` (field ~90, `setObservablePropValue_` ~182-203, `delete_` ~510-566, `notifyPropertyAddition_` ~574-599) + +**Remove `*DidChange` change-event types** (now unused after spy + observe removal) and their `mobx.ts` exports: + +- `IValueDidChange` (`observablevalue.ts`; `mobx.ts:53`) +- `IArrayDidChange`, `IArrayUpdate`, `IArraySplice`, `IArrayBaseChange` (`observablearray.ts`; `mobx.ts:60,61,62`) +- `IMapDidChange` (`observablemap.ts`; `mobx.ts:69`) +- `ISetDidChange` (`observableset.ts`; `mobx.ts:74`) +- `IObjectDidChange` (`observableobject.ts`; `mobx.ts:51`) + +Let `tsc --noEmit` confirm each type is truly unused before deleting. + +**Tests** + +- Delete `__tests__/base/observe.ts`. +- Remove `observe`-based tests / probes from: `base/observables.js` (boxed/computed `.observe`), `base/map.js`, `base/set.js`, `base/array.js` (change-event tests), `base/object-api.js` (~423-477, the remaining observe half), `base/makereactive.js` (~165-198), `base/tojs.js` (~72-85), `base/errorhandling.js` (~248, 285 — keep the cycle test, replace the `observe` probe with an `autorun`/`reaction` equivalent if it's load-bearing), `base/typescript-tests.ts`, `base/babel-tests.js` (~193-195, 513), `base/stage3-decorators.ts`, `base/stage3-decorators-inheritance.ts`, `perf/perf.js` (~52). +- `base/api.js`: remove `"observe"` from expected exports. +- Prune affected snapshots. + +**Docs** + +- Delete `docs/intercept-and-observe.md` and remove its sidebar/nav entry. +- `docs/api.md`: remove the `observe` entry (~288-293). + +**Commit**: `git commit -am "refactor(mobx): remove observe API and change-listener machinery"` + +--- + +## Commit 5 — Remove object API (`get/has/set/remove/keys/values/entries/ownKeys`) + +**Edit `src/api/object-api.ts`** — remove the functions `keys`, `values`, `entries`, `set`, `remove`, `has`, `get`, and `apiOwnKeys` (with their overload signatures) plus any now-unused imports. **Keep `apiDefineProperty`** (exported as `defineProperty`) and whatever it imports. The file continues to exist. + +**Exports** + +- `mobx.ts:106-113`: remove `keys, values, entries, set, remove, has, get, apiOwnKeys as ownKeys`. Keep `apiDefineProperty as defineProperty`. +- `internal.ts:42`: leave `export * from "./api/object-api"` (still exports `apiDefineProperty`). + +**Errors**: remove codes `5, 6, 7, 8, 9, 10, 11, 38` (`errors.ts:17-23, 69`). Keep `39` (defineProperty). Before deleting code `42` — do NOT; the object-api `set()` used `die(42)` but code 42 ("Invalid index") is also used by array internals — verify with `grep -rn "die(42)" packages/mobx/src` and keep it (expected: still referenced). + +**Keep** the admin methods `keys_`, `set_`, `delete_`, `has_`, `get_`, `ownKeys_`, `defineProperty_` on `ObservableObjectAdministration` — required by `dynamicobject.ts` proxy traps. + +**Tests** + +- Delete `__tests__/base/object-api.js` (by now only object-API tests remain in it, after commits 2 & 4 stripped the observe/intercept parts). If any non-object-API test snuck in, migrate it out first. +- `base/map.js`, `base/set.js`: replace `mobx.keys(x)/mobx.values(x)/mobx.entries(x)` with the instance-method equivalents (`[...x.keys()]`, `[...x.values()]`, `[...x.entries()]`) — these tests exercise Map/Set behavior; keep them, just swap the helper. +- `base/proxies.js` (~132, 137): replace incidental `keys(x)` usage. +- `base/typescript-tests.ts` (~1729): remove/replace incidental `mobx.keys(new B())`. +- `base/api.js`: remove `get, has, set, remove, keys, values, entries, ownKeys` from expected exports. + +**Docs** + +- Delete `docs/collection-utilities.md` and remove its sidebar/nav entry. +- `docs/api.md`: remove the `values/keys/entries/set/remove/has/get` entries (~350-397). + +**Commit**: `git commit -am "refactor(mobx): remove top-level object API (get/set/has/remove/keys/values/entries/ownKeys)"` + +--- + +## Commit 6 — Remove introspection (`getDebugName/getDependencyTree/getObserverTree`) + downstream fix + +**Delete** + +- `src/api/extras.ts`: `getDependencyTree` (~13-15), `getObserverTree` (~27-29), private helpers `nodeToDependencyTree` (~17-25), `nodeToObserverTree` (~31-39), `unique` (~41-43, if unused elsewhere), and types `IDependencyTree` (~3-6), `IObserverTree` (~8-11). If the file becomes empty, delete it and its `internal.ts:36` re-export; otherwise leave the remaining exports. +- `getDebugName` in `src/types/type-utils.ts` (~91-104). Keep `getAtom`/`getAdministration` (broadly used). +- `mobx.ts`: remove `IObserverTree, IDependencyTree, getDependencyTree, getObserverTree` (~126-129) and `getDebugName` (~132). Confirm the devtools hook (already removed in commit 1) leaves no dangling `getDebugName` reference. + +**Downstream fix** + +- `packages/mobx-react-lite/src/useObserver.ts`: remove the `getDependencyTree` import (line 1) and the `React.useDebugValue(adm.reaction!, getDependencyTree)` call (line ~90). Drop the debug-value line entirely (or replace with `React.useDebugValue(adm.reaction!)`). + +**Tests** + +- `__tests__/base/extras.js`: remove the `"treeD"` test (~6-81) and the `getDebugName` tests (~217-263, ~878-964). If nothing meaningful remains, delete the file. +- `base/make-observable.ts` (~588-589): remove the incidental `getDebugName` usage. +- `base/api.js`: remove `getDebugName, getDependencyTree, getObserverTree` from expected exports. + +**Docs** + +- Delete `docs/analyzing-reactivity.md` (spy section already gone in commit 1; remove the rest) and its sidebar/nav entry. +- `docs/api.md`: remove `getDebugName/getDependencyTree/getObserverTree` entries (~472-491). +- `docs/understanding-reactivity.md` (~71-83): remove the `getDependencyTree` import + code sample. +- Grep the docs sidebar/site config (e.g. `sidebars*.js`, `*.json`, docusaurus config) for the deleted page filenames and remove those nav entries. + +**Verify** the full monorepo builds (mobx + mobx-react-lite) in addition to the standard per-commit checks. + +**Commit**: `git commit -am "refactor(mobx): remove getDebugName/getDependencyTree/getObserverTree introspection APIs"` + +--- + +## Anticipated trouble / risks + +1. **`spy` is the most invasive** — it is woven through `action`, `reaction`, `computedvalue`, and all five observable types. Doing it first (commit 1) isolates the churn. Watch for now-dead imports/constants after the `spyReport` blocks are removed. +2. **`base/api.js` export-list guard** fails the _entire_ suite the moment any export is removed. Update its expected array in the same commit that removes each export. +3. **Snapshot tests** (`spy`, `extras`, `object-api`, `observables`, `flow`, decorator files) will drift; regenerate with `jest -u` or delete obsolete snapshot entries. Review the diff — don't blindly `-u`. +4. **Instance-method vs top-level name collision**: many tests call `.get()/.set()/.has()/.keys()` on Observable Map/Set as _methods_; only `mobx.get(...)` etc. are being removed. Grep carefully to avoid touching valid instance-method calls. Likewise `array.remove(...)` stays. +5. **`observe` in error-handling/cycle tests** (`base/errorhandling.js`) may be load-bearing for triggering reactions, not just probing. Replace with `autorun`/`reaction` rather than deleting the test outright. +6. **Change-event type deletion timing**: `*WillChange` are safe to delete with `intercept` (commit 2); `*DidChange` must wait until BOTH `spy` and `observe` are gone (commit 4). Rely on `tsc --noEmit` to confirm zero references before each type deletion. +7. **Error-code renumbering**: codes are a sparse map (2–4 already commented out), so removing keys is safe without renumbering. But verify `die(42)` (used by object-api `set` AND array internals) stays — grep before removing any code. +8. **`raw()` removal** (commit 3): `ObservableValue.raw()` existed for mobx-state-tree to read the un-dehanced value. Removing the dehancer removes its reason to exist; external MST versions relying on it would break — acceptable for this major cleanup, but worth flagging in the changelog. +9. **Devtools hook** (`__MOBX_DEVTOOLS_GLOBAL_HOOK__`) depends on `spy` + `getDebugName`; it is removed wholesale in commit 1. `mobx-devtools` will no longer receive data — intended given `spy` is gone. +10. **Changesets**: this repo uses Changesets (`.changeset/`). Add a `major` changeset describing the removals so the release notes are correct. From 577ec1d0150fbe3fe457094b7d00a0d038318a40 Mon Sep 17 00:00:00 2001 From: Michel Weststrate Date: Thu, 30 Jul 2026 22:06:30 +0200 Subject: [PATCH 02/10] docs: incorporate globalState/error-code/api.js clarifications into plan --- API_REMOVAL_PLAN.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/API_REMOVAL_PLAN.md b/API_REMOVAL_PLAN.md index 08a534b3d..16c7aba32 100644 --- a/API_REMOVAL_PLAN.md +++ b/API_REMOVAL_PLAN.md @@ -42,9 +42,11 @@ npx eslint src/**/* # lint (catches unused imports) For commit 6 also build the whole monorepo / mobx-react-lite so the downstream fix is validated. -When removing an error code, first confirm no other caller: `grep -rn "die()" packages/mobx/src`. +**Error codes**: error codes that are no longer thrown after a removal are cleaned up (deleted from `errors.ts`) in the same commit. Before deleting a code, confirm no remaining caller: `grep -rn "die()" packages/mobx/src`. Codes are a sparse map, so removing keys needs no renumbering. -`base/api.js` (`__tests__/base/api.js`) asserts the exact set of `Object.keys(mobx)`. Every export removed below must also be deleted from its expected array — do this in the same commit or the whole suite fails. +**globalState cleanup**: `globalState` (`core/globalstate.ts`) is a public object, but fields that no longer make sense after a removal ARE cleaned up. Any commit that changes the shape of `MobXGlobals` (add/remove a field, or change `persistentKeys`) MUST bump `MOBX_GLOBALS_VERSION` (`core/globalstate.ts:4`) so the multi-version-in-memory guard (`globalstate.ts:158`) stays correct. In this plan only commit 1 (removing `spyListeners`) touches globalState, so bump `7` → `8` there. + +**`base/api.js`** (`__tests__/base/api.js`) asserts the exact set of `Object.keys(mobx)`. It is updated **per commit** to reflect the export surface after that commit — this incremental change is intended. Every export removed in a commit must be deleted from its expected array in that same commit, or the whole suite fails. --- @@ -70,7 +72,7 @@ Each commit is independently green. Commits 1–5 are largely independent; do th - `src/core/spy.ts` entirely (`isSpyEnabled`, `spyReport`, `spyReportStart`, `spyReportEnd`, `END_EVENT`, `PureSpyEvent`, `SpyEvent`, `spy`). - `internal.ts:30` (`export * from "./core/spy"`). - `mobx.ts:39` (`spy` export) and the whole **Devtools hook** block (`mobx.ts:158-167`) — it is spy-centric; this also drops its `getDebugName` reference (removed in commit 6) and the `spy` import at `mobx.ts:28`. -- `globalState.spyListeners` field (`core/globalstate.ts:106`) and its entry in `persistentKeys` (`core/globalstate.ts:11`). +- `globalState.spyListeners` field (`core/globalstate.ts:106`) and its entry in `persistentKeys` (`core/globalstate.ts:11`). Then **bump `MOBX_GLOBALS_VERSION`** (`core/globalstate.ts:4`) from `7` → `8`, since the internal state shape changed (see "globalState cleanup" note below). - Spy-only change type `IComputedDidChange` (`core/computedvalue.ts:47-54`) — verify no remaining use (`observe` builds its computed-change object inline, so it does not need the type; confirm before deleting). - `IBoxDidChange` (`observablevalue.ts:39-47`) — spy-only, not exported. @@ -283,7 +285,7 @@ Let `tsc --noEmit` confirm each type is truly unused before deleting. 4. **Instance-method vs top-level name collision**: many tests call `.get()/.set()/.has()/.keys()` on Observable Map/Set as _methods_; only `mobx.get(...)` etc. are being removed. Grep carefully to avoid touching valid instance-method calls. Likewise `array.remove(...)` stays. 5. **`observe` in error-handling/cycle tests** (`base/errorhandling.js`) may be load-bearing for triggering reactions, not just probing. Replace with `autorun`/`reaction` rather than deleting the test outright. 6. **Change-event type deletion timing**: `*WillChange` are safe to delete with `intercept` (commit 2); `*DidChange` must wait until BOTH `spy` and `observe` are gone (commit 4). Rely on `tsc --noEmit` to confirm zero references before each type deletion. -7. **Error-code renumbering**: codes are a sparse map (2–4 already commented out), so removing keys is safe without renumbering. But verify `die(42)` (used by object-api `set` AND array internals) stays — grep before removing any code. -8. **`raw()` removal** (commit 3): `ObservableValue.raw()` existed for mobx-state-tree to read the un-dehanced value. Removing the dehancer removes its reason to exist; external MST versions relying on it would break — acceptable for this major cleanup, but worth flagging in the changelog. +7. **Error codes**: codes are a sparse map (2–4 already commented out), so removing keys needs no renumbering. Delete the no-longer-thrown codes in the same commit as the feature. Verify `die(42)` (used by object-api `set` AND array internals) stays — grep before removing any code. +8. **`dehancer` / `raw()` removal** (commit 3): the dehancer and `ObservableValue.raw()` existed for mobx-state-tree to read un-dehanced values. MST is **no longer supported**, so removing them wholesale is intended (not just acceptable). Note it in the changeset. 9. **Devtools hook** (`__MOBX_DEVTOOLS_GLOBAL_HOOK__`) depends on `spy` + `getDebugName`; it is removed wholesale in commit 1. `mobx-devtools` will no longer receive data — intended given `spy` is gone. 10. **Changesets**: this repo uses Changesets (`.changeset/`). Add a `major` changeset describing the removals so the release notes are correct. From 6ea7ab3ef51e65fae2526064d89ea4767eddc432 Mon Sep 17 00:00:00 2001 From: Michel Weststrate Date: Thu, 30 Jul 2026 22:23:36 +0200 Subject: [PATCH 03/10] refactor(mobx): remove spy API and spy reporting machinery --- docs/analyzing-reactivity.md | 40 -- docs/api.md | 7 - .../base/__snapshots__/extras.js.snap | 34 -- .../__tests__/base/__snapshots__/flow.js.snap | 106 ----- .../base/__snapshots__/observables.js.snap | 209 --------- .../__tests__/base/__snapshots__/spy.js.snap | 418 ------------------ packages/mobx/__tests__/base/action.js | 18 - packages/mobx/__tests__/base/api.js | 1 - packages/mobx/__tests__/base/babel-tests.js | 97 ---- packages/mobx/__tests__/base/extras.js | 31 -- packages/mobx/__tests__/base/flow.js | 32 -- packages/mobx/__tests__/base/observables.js | 59 --- packages/mobx/__tests__/base/spy.js | 177 -------- .../mobx/__tests__/base/stage3-decorators.ts | 97 ---- .../mobx/__tests__/base/typescript-tests.ts | 97 ---- packages/mobx/src/api/observable.ts | 2 +- packages/mobx/src/core/action.ts | 24 - packages/mobx/src/core/computedvalue.ts | 22 - packages/mobx/src/core/globalstate.ts | 8 +- packages/mobx/src/core/reaction.ts | 34 -- packages/mobx/src/core/spy.ts | 71 --- packages/mobx/src/internal.ts | 1 - packages/mobx/src/mobx.ts | 16 - .../mobx/src/types/observableannotation.ts | 3 +- packages/mobx/src/types/observablearray.ts | 73 ++- packages/mobx/src/types/observablemap.ts | 98 ++-- packages/mobx/src/types/observableobject.ts | 91 ++-- packages/mobx/src/types/observableset.ts | 57 +-- packages/mobx/src/types/observablevalue.ts | 42 -- 29 files changed, 110 insertions(+), 1855 deletions(-) delete mode 100644 packages/mobx/__tests__/base/__snapshots__/extras.js.snap delete mode 100644 packages/mobx/__tests__/base/__snapshots__/flow.js.snap delete mode 100644 packages/mobx/__tests__/base/__snapshots__/observables.js.snap delete mode 100644 packages/mobx/__tests__/base/__snapshots__/spy.js.snap delete mode 100644 packages/mobx/__tests__/base/spy.js delete mode 100644 packages/mobx/src/core/spy.ts diff --git a/docs/analyzing-reactivity.md b/docs/analyzing-reactivity.md index fb230add3..272c22409 100644 --- a/docs/analyzing-reactivity.md +++ b/docs/analyzing-reactivity.md @@ -44,43 +44,3 @@ Usage: - `getAtom(thing, property?)`. Returns the backing _Atom_ of a given observable object, property, reaction etc. - -# Spy - -Usage: - -- `spy(listener)` - -Registers a global spy listener that listens to all events that happen in MobX. -It is similar to attaching an `observe` listener to _all_ observables at once, but also notifies about running (trans/re)actions and computations. -Used for example by the [MobX developer tools](https://github.com/mobxjs/mobx-devtools). - -Example usage of spying all actions: - -```javascript -spy(event => { - if (event.type === "action") { - console.log(`${event.name} with args: ${event.arguments}`) - } -}) -``` - -Spy listeners always receive one object, which usually has at least a `type` field. The following events are emitted by default by spy: - -| Type | observableKind | Other fields | Nested | -| ------------------------------- | -------------- | -------------------------------------------------------------- | ------ | -| action | | name, object (scope), arguments[] | yes | -| scheduled-reaction | | name | no | -| reaction | | name | yes | -| error | | name, message, error | no | -| add,update,remove,delete,splice | | Check out [Intercept & observe {🚀}](intercept-and-observe.md) | yes | -| report-end | | spyReportEnd=true, time? (total execution time in ms) | no | - -The `report-end` events are part of an earlier fired event that had `spyReportStart: true`. -This event indicates the end of an event and this way groups of events with sub-events are created. -This event might report the total execution time as well. - -The spy events for observable values are identical to the events passed to `observe`. -In production builds, the `spy` API is a no-op as it will be minimized away. - -Check out the [Intercept & observe {🚀}](intercept-and-observe.md#event-overview) section for an extensive overview. diff --git a/docs/api.md b/docs/api.md index 9f3fc3246..4e7190b1d 100644 --- a/docs/api.md +++ b/docs/api.md @@ -462,13 +462,6 @@ Is this a boxed computed value, created using `computed(() => expr)`? Is this a computed property? -### `spy` - -{🚀} Usage: `spy(eventListener)` -([further information](analyzing-reactivity.md#spy)) - -Registers a global spy listener that listens to all events that happen in MobX. - ### `getDebugName` {🚀} Usage: `getDebugName(reaction|array|Set|Map)` or `getDebugName(object|Map, propertyName)` diff --git a/packages/mobx/__tests__/base/__snapshots__/extras.js.snap b/packages/mobx/__tests__/base/__snapshots__/extras.js.snap deleted file mode 100644 index a40f7d50d..000000000 --- a/packages/mobx/__tests__/base/__snapshots__/extras.js.snap +++ /dev/null @@ -1,34 +0,0 @@ -// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing - -exports[`spy 1 1`] = ` -[ - { - "debugObjectName": "ObservableValue@5", - "newValue": 4, - "observableKind": "value", - "oldValue": 3, - "spyReportStart": true, - "type": "update", - }, - { - "debugObjectName": "ComputedValue@6", - "newValue": 8, - "observableKind": "computed", - "oldValue": 6, - "type": "update", - }, - { - "name": "Autorun@7", - "spyReportStart": true, - "type": "reaction", - }, - { - "spyReportEnd": true, - "type": "report-end", - }, - { - "spyReportEnd": true, - "type": "report-end", - }, -] -`; diff --git a/packages/mobx/__tests__/base/__snapshots__/flow.js.snap b/packages/mobx/__tests__/base/__snapshots__/flow.js.snap deleted file mode 100644 index a3c1c0602..000000000 --- a/packages/mobx/__tests__/base/__snapshots__/flow.js.snap +++ /dev/null @@ -1,106 +0,0 @@ -// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing - -exports[`it should support logging 1`] = ` -[ - { - "arguments": [ - 2, - ], - "name": "myaction - runid: 6 - init", - "spyReportStart": true, - "type": "action", - }, - { - "spyReportEnd": true, - "type": "report-end", - }, - { - "arguments": [ - undefined, - ], - "name": "myaction - runid: 6 - yield 0", - "spyReportStart": true, - "type": "action", - }, - { - "debugObjectName": "ObservableObject@7", - "name": "a", - "newValue": 2, - "observableKind": "object", - "oldValue": 1, - "spyReportStart": true, - "type": "update", - }, - { - "spyReportEnd": true, - "type": "report-end", - }, - { - "spyReportEnd": true, - "type": "report-end", - }, - { - "arguments": [ - 5, - ], - "name": "myaction - runid: 6 - yield 1", - "spyReportStart": true, - "type": "action", - }, - { - "debugObjectName": "ObservableObject@7", - "name": "a", - "newValue": 5, - "observableKind": "object", - "oldValue": 2, - "spyReportStart": true, - "type": "update", - }, - { - "spyReportEnd": true, - "type": "report-end", - }, - { - "debugObjectName": "ObservableObject@7", - "name": "a", - "newValue": 4, - "observableKind": "object", - "oldValue": 5, - "spyReportStart": true, - "type": "update", - }, - { - "spyReportEnd": true, - "type": "report-end", - }, - { - "spyReportEnd": true, - "type": "report-end", - }, - { - "arguments": [ - 3, - ], - "name": "myaction - runid: 6 - yield 2", - "spyReportStart": true, - "type": "action", - }, - { - "debugObjectName": "ObservableObject@7", - "name": "a", - "newValue": 3, - "observableKind": "object", - "oldValue": 4, - "spyReportStart": true, - "type": "update", - }, - { - "spyReportEnd": true, - "type": "report-end", - }, - { - "spyReportEnd": true, - "type": "report-end", - }, -] -`; diff --git a/packages/mobx/__tests__/base/__snapshots__/observables.js.snap b/packages/mobx/__tests__/base/__snapshots__/observables.js.snap deleted file mode 100644 index 1fc3d3ed5..000000000 --- a/packages/mobx/__tests__/base/__snapshots__/observables.js.snap +++ /dev/null @@ -1,209 +0,0 @@ -// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing - -exports[`delay autorun until end of transaction 1`] = ` -[ - { - "debugObjectName": "ObservableObject@1", - "name": "a", - "newValue": 3, - "observableKind": "object", - "oldValue": 2, - "spyReportStart": true, - "type": "update", - }, - { - "spyReportEnd": true, - "type": "report-end", - }, - { - "debugObjectName": "ObservableObject@1", - "name": "a", - "newValue": 4, - "observableKind": "object", - "oldValue": 3, - "spyReportStart": true, - "type": "update", - }, - { - "spyReportEnd": true, - "type": "report-end", - }, - "end1", - { - "debugObjectName": "ObservableObject@1", - "name": "a", - "newValue": 5, - "observableKind": "object", - "oldValue": 4, - "spyReportStart": true, - "type": "update", - }, - { - "spyReportEnd": true, - "type": "report-end", - }, - "end2", - { - "name": "test", - "spyReportStart": true, - "type": "reaction", - }, - "auto", - "calc y", - { - "debugObjectName": "ObservableObject@1.b", - "newValue": 5, - "observableKind": "computed", - "oldValue": CaughtException { - "cause": null, - }, - "type": "update", - }, - { - "spyReportEnd": true, - "type": "report-end", - }, - "post trans1", - { - "debugObjectName": "ObservableObject@1", - "name": "a", - "newValue": 6, - "observableKind": "object", - "oldValue": 5, - "spyReportStart": true, - "type": "update", - }, - "calc y", - { - "debugObjectName": "ObservableObject@1.b", - "newValue": 6, - "observableKind": "computed", - "oldValue": 5, - "type": "update", - }, - { - "name": "test", - "spyReportStart": true, - "type": "reaction", - }, - "auto", - { - "spyReportEnd": true, - "type": "report-end", - }, - { - "spyReportEnd": true, - "type": "report-end", - }, - "post trans2", - { - "debugObjectName": "ObservableObject@1", - "name": "a", - "newValue": 3, - "observableKind": "object", - "oldValue": 6, - "spyReportStart": true, - "type": "update", - }, - { - "spyReportEnd": true, - "type": "report-end", - }, - "post trans3", -] -`; - -exports[`issue 50 1`] = ` -[ - "auto", - "calc c", - "transstart", - { - "debugObjectName": "ObservableObject@1", - "name": "a", - "newValue": false, - "observableKind": "object", - "oldValue": true, - "spyReportStart": true, - "type": "update", - }, - { - "spyReportEnd": true, - "type": "report-end", - }, - { - "debugObjectName": "ObservableObject@1", - "name": "b", - "newValue": true, - "observableKind": "object", - "oldValue": false, - "spyReportStart": true, - "type": "update", - }, - { - "spyReportEnd": true, - "type": "report-end", - }, - "transpreend", - { - "name": "ar", - "spyReportStart": true, - "type": "reaction", - }, - "auto", - "calc c", - { - "debugObjectName": "ObservableObject@1.c", - "newValue": true, - "observableKind": "computed", - "oldValue": false, - "type": "update", - }, - { - "spyReportEnd": true, - "type": "report-end", - }, - "transpostend", -] -`; - -exports[`verify transaction events 1`] = ` -[ - "auto", - "calc c", - "transstart", - { - "debugObjectName": "ObservableObject@1", - "name": "b", - "newValue": 2, - "observableKind": "object", - "oldValue": 1, - "spyReportStart": true, - "type": "update", - }, - { - "spyReportEnd": true, - "type": "report-end", - }, - "transpreend", - "calc c", - { - "debugObjectName": "ObservableObject@1.c", - "newValue": 2, - "observableKind": "computed", - "oldValue": 1, - "type": "update", - }, - { - "name": "ar", - "spyReportStart": true, - "type": "reaction", - }, - "auto", - { - "spyReportEnd": true, - "type": "report-end", - }, - "transpostend", -] -`; diff --git a/packages/mobx/__tests__/base/__snapshots__/spy.js.snap b/packages/mobx/__tests__/base/__snapshots__/spy.js.snap deleted file mode 100644 index 646f594bf..000000000 --- a/packages/mobx/__tests__/base/__snapshots__/spy.js.snap +++ /dev/null @@ -1,418 +0,0 @@ -// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing - -exports[`spy error 1`] = ` -[ - { - "name": "autorun", - "spyReportStart": true, - "type": "reaction", - }, - { - "debugObjectName": "ObservableObject@1.y", - "newValue": 4, - "observableKind": "computed", - "oldValue": CaughtException { - "cause": null, - }, - "type": "update", - }, - { - "spyReportEnd": true, - "type": "report-end", - }, - { - "debugObjectName": "ObservableObject@1", - "name": "x", - "newValue": 3, - "observableKind": "object", - "oldValue": 2, - "spyReportStart": true, - "type": "update", - }, - { - "debugObjectName": "ObservableObject@1.y", - "newValue": CaughtException { - "cause": "Oops", - }, - "observableKind": "computed", - "oldValue": 4, - "type": "update", - }, - { - "name": "autorun", - "spyReportStart": true, - "type": "reaction", - }, - { - "error": "Oops", - "message": "[mobx] Encountered an uncaught exception that was thrown by a reaction or observer component, in: 'Reaction[autorun]'", - "name": "autorun", - "type": "error", - }, - { - "spyReportEnd": true, - "type": "report-end", - }, - { - "spyReportEnd": true, - "type": "report-end", - }, - { - "arguments": [ - 4, - ], - "name": "setX", - "spyReportStart": true, - "type": "action", - }, - { - "debugObjectName": "ObservableObject@1", - "name": "x", - "newValue": 4, - "observableKind": "object", - "oldValue": 3, - "spyReportStart": true, - "type": "update", - }, - { - "spyReportEnd": true, - "type": "report-end", - }, - { - "debugObjectName": "ObservableObject@1.y", - "newValue": 8, - "observableKind": "computed", - "oldValue": CaughtException { - "cause": "Oops", - }, - "type": "update", - }, - { - "name": "autorun", - "spyReportStart": true, - "type": "reaction", - }, - { - "spyReportEnd": true, - "type": "report-end", - }, - { - "spyReportEnd": true, - "type": "report-end", - }, -] -`; - -exports[`spy output 1`] = ` -[ - { - "debugObjectName": "ObservableValue@1", - "newValue": "2", - "observableKind": "value", - "type": "create", - }, - { - "debugObjectName": "ObservableValue@1", - "newValue": 3, - "observableKind": "value", - "oldValue": 2, - "spyReportStart": true, - "type": "update", - }, - { - "spyReportEnd": true, - "type": "report-end", - }, - { - "debugObjectName": "ObservableObject@2", - "name": "c", - "newValue": 4, - "observableKind": "object", - "spyReportStart": true, - "type": "add", - }, - { - "spyReportEnd": true, - "type": "report-end", - }, - { - "debugObjectName": "ObservableObject@2", - "name": "c", - "newValue": 5, - "observableKind": "object", - "oldValue": 4, - "spyReportStart": true, - "type": "update", - }, - { - "spyReportEnd": true, - "type": "report-end", - }, - { - "debugObjectName": "ObservableObject@2", - "name": "d", - "newValue": 6, - "observableKind": "object", - "spyReportStart": true, - "type": "add", - }, - { - "spyReportEnd": true, - "type": "report-end", - }, - { - "debugObjectName": "ObservableObject@2", - "name": "d", - "newValue": 7, - "observableKind": "object", - "oldValue": 6, - "spyReportStart": true, - "type": "update", - }, - { - "spyReportEnd": true, - "type": "report-end", - }, - { - "added": [ - 1, - 2, - ], - "addedCount": 2, - "debugObjectName": "ObservableArray@3", - "index": 0, - "observableKind": "array", - "removed": [], - "removedCount": 0, - "spyReportStart": true, - "type": "splice", - }, - { - "spyReportEnd": true, - "type": "report-end", - }, - { - "added": [ - 3, - 4, - ], - "addedCount": 2, - "debugObjectName": "ObservableArray@3", - "index": 2, - "observableKind": "array", - "removed": [], - "removedCount": 0, - "spyReportStart": true, - "type": "splice", - }, - { - "spyReportEnd": true, - "type": "report-end", - }, - { - "added": [], - "addedCount": 0, - "debugObjectName": "ObservableArray@3", - "index": 0, - "observableKind": "array", - "removed": [ - 1, - ], - "removedCount": 1, - "spyReportStart": true, - "type": "splice", - }, - { - "spyReportEnd": true, - "type": "report-end", - }, - { - "debugObjectName": "ObservableArray@3", - "index": 2, - "newValue": 5, - "observableKind": "array", - "oldValue": 4, - "spyReportStart": true, - "type": "update", - }, - { - "spyReportEnd": true, - "type": "report-end", - }, - { - "debugObjectName": "ObservableMap@4", - "name": "g", - "newValue": 1, - "observableKind": "map", - "spyReportStart": true, - "type": "add", - }, - { - "spyReportEnd": true, - "type": "report-end", - }, - { - "debugObjectName": "ObservableMap@4", - "name": "g", - "observableKind": "map", - "oldValue": 1, - "spyReportStart": true, - "type": "delete", - }, - { - "spyReportEnd": true, - "type": "report-end", - }, - { - "debugObjectName": "ObservableMap@4", - "name": "i", - "newValue": 5, - "observableKind": "map", - "spyReportStart": true, - "type": "add", - }, - { - "spyReportEnd": true, - "type": "report-end", - }, - { - "debugObjectName": "ObservableMap@4", - "name": "i", - "newValue": 6, - "observableKind": "map", - "oldValue": 5, - "spyReportStart": true, - "type": "update", - }, - { - "spyReportEnd": true, - "type": "report-end", - }, - { - "name": "Autorun@6", - "spyReportStart": true, - "type": "reaction", - }, - { - "debugObjectName": "ComputedValue@5", - "newValue": 6, - "observableKind": "computed", - "oldValue": CaughtException { - "cause": null, - }, - "type": "update", - }, - { - "spyReportEnd": true, - "type": "report-end", - }, - { - "debugObjectName": "ObservableValue@1", - "newValue": 4, - "observableKind": "value", - "oldValue": 3, - "spyReportStart": true, - "type": "update", - }, - { - "debugObjectName": "ComputedValue@5", - "newValue": 8, - "observableKind": "computed", - "oldValue": 6, - "type": "update", - }, - { - "name": "Autorun@6", - "spyReportStart": true, - "type": "reaction", - }, - { - "spyReportEnd": true, - "type": "report-end", - }, - { - "spyReportEnd": true, - "type": "report-end", - }, - { - "debugObjectName": "ObservableValue@1", - "newValue": 5, - "observableKind": "value", - "oldValue": 4, - "spyReportStart": true, - "type": "update", - }, - { - "spyReportEnd": true, - "type": "report-end", - }, - { - "debugObjectName": "ObservableValue@1", - "newValue": 6, - "observableKind": "value", - "oldValue": 5, - "spyReportStart": true, - "type": "update", - }, - { - "spyReportEnd": true, - "type": "report-end", - }, - { - "debugObjectName": "ComputedValue@5", - "newValue": 12, - "observableKind": "computed", - "oldValue": 8, - "type": "update", - }, - { - "name": "Autorun@6", - "spyReportStart": true, - "type": "reaction", - }, - { - "spyReportEnd": true, - "type": "report-end", - }, - { - "arguments": [ - 7, - ], - "name": "myTestAction", - "spyReportStart": true, - "type": "action", - }, - { - "debugObjectName": "ObservableValue@1", - "newValue": 7, - "observableKind": "value", - "oldValue": 6, - "spyReportStart": true, - "type": "update", - }, - { - "spyReportEnd": true, - "type": "report-end", - }, - { - "debugObjectName": "ComputedValue@5", - "newValue": 14, - "observableKind": "computed", - "oldValue": 12, - "type": "update", - }, - { - "name": "Autorun@6", - "spyReportStart": true, - "type": "reaction", - }, - { - "spyReportEnd": true, - "type": "report-end", - }, - { - "spyReportEnd": true, - "type": "report-end", - }, -] -`; diff --git a/packages/mobx/__tests__/base/action.js b/packages/mobx/__tests__/base/action.js index 78352213a..73983173b 100644 --- a/packages/mobx/__tests__/base/action.js +++ b/packages/mobx/__tests__/base/action.js @@ -307,14 +307,6 @@ test("#286 exceptions in actions should not affect global state", () => { test("runInAction", () => { mobx.configure({ enforceActions: "observed" }) const values = [] - const events = [] - const spyDisposer = mobx.spy(ev => { - if (ev.type === "action") - events.push({ - name: ev.name, - arguments: ev.arguments - }) - }) const observable = mobx.observable.box(0) const d = mobx.autorun(() => values.push(observable.get())) @@ -336,12 +328,6 @@ test("runInAction", () => { expect(res).toBe(3) expect(values).toEqual([0, 9, 15]) - expect(events).toEqual([ - { arguments: [], name: "" }, - { arguments: [], name: "" } - ]) - - spyDisposer() d() }) @@ -467,19 +453,15 @@ test("bound actions bind", () => { const d = mobx.autorun(() => { x.yValue }) - const events = [] - const d2 = mobx.spy(e => events.push(e)) const runner = x.z runner(3) expect(x.yValue).toBe(6) expect(called).toBe(2) - expect(events.filter(e => e.type === "action").map(e => e.name)).toEqual(["z"]) expect(Object.keys(x)).toEqual(["y"]) d() - d2() }) test("Fix #1367", () => { diff --git a/packages/mobx/__tests__/base/api.js b/packages/mobx/__tests__/base/api.js index 4c64fdd14..e248dc62c 100644 --- a/packages/mobx/__tests__/base/api.js +++ b/packages/mobx/__tests__/base/api.js @@ -75,7 +75,6 @@ test("correct api should be exposed", function () { "_resetGlobalState", "runInAction", "set", - "spy", "toJS", "transaction", "untracked", diff --git a/packages/mobx/__tests__/base/babel-tests.js b/packages/mobx/__tests__/base/babel-tests.js index 12185b658..b2765e80e 100644 --- a/packages/mobx/__tests__/base/babel-tests.js +++ b/packages/mobx/__tests__/base/babel-tests.js @@ -15,7 +15,6 @@ import { isObservable, isObservableProp, isComputedProp, - spy, isAction, configure, makeObservable @@ -295,14 +294,6 @@ test("705 - setter undoing caching (babel)", () => { d2() }) -function normalizeSpyEvents(events) { - events.forEach(ev => { - delete ev.fn - delete ev.time - }) - return events -} - test("action decorator (babel)", function () { class Store { constructor(multiplier) { @@ -320,22 +311,9 @@ test("action decorator (babel)", function () { const store1 = new Store(2) const store2 = new Store(3) - const events = [] - const d = spy(events.push.bind(events)) expect(store1.add(3, 4)).toBe(14) expect(store2.add(3, 4)).toBe(21) expect(store1.add(1, 1)).toBe(4) - - expect(normalizeSpyEvents(events)).toEqual([ - { arguments: [3, 4], name: "add", spyReportStart: true, object: store1, type: "action" }, - { type: "report-end", spyReportEnd: true }, - { arguments: [3, 4], name: "add", spyReportStart: true, object: store2, type: "action" }, - { type: "report-end", spyReportEnd: true }, - { arguments: [1, 1], name: "add", spyReportStart: true, object: store1, type: "action" }, - { type: "report-end", spyReportEnd: true } - ]) - - d() }) test("custom action decorator (babel)", function () { @@ -355,40 +333,9 @@ test("custom action decorator (babel)", function () { const store1 = new Store(2) const store2 = new Store(3) - const events = [] - const d = spy(events.push.bind(events)) expect(store1.add(3, 4)).toBe(14) expect(store2.add(3, 4)).toBe(21) expect(store1.add(1, 1)).toBe(4) - - expect(normalizeSpyEvents(events)).toEqual([ - { - arguments: [3, 4], - name: "zoem zoem", - spyReportStart: true, - object: store1, - type: "action" - }, - { type: "report-end", spyReportEnd: true }, - { - arguments: [3, 4], - name: "zoem zoem", - spyReportStart: true, - object: store2, - type: "action" - }, - { type: "report-end", spyReportEnd: true }, - { - arguments: [1, 1], - name: "zoem zoem", - spyReportStart: true, - object: store1, - type: "action" - }, - { type: "report-end", spyReportEnd: true } - ]) - - d() }) test("action decorator on field (babel)", function () { @@ -409,22 +356,9 @@ test("action decorator on field (babel)", function () { const store1 = new Store(2) const store2 = new Store(7) - const events = [] - const d = spy(events.push.bind(events)) expect(store1.add(3, 4)).toBe(14) expect(store2.add(5, 4)).toBe(63) expect(store1.add(2, 2)).toBe(8) - - expect(normalizeSpyEvents(events)).toEqual([ - { arguments: [3, 4], name: "add", spyReportStart: true, object: store1, type: "action" }, - { type: "report-end", spyReportEnd: true }, - { arguments: [5, 4], name: "add", spyReportStart: true, object: store2, type: "action" }, - { type: "report-end", spyReportEnd: true }, - { arguments: [2, 2], name: "add", spyReportStart: true, object: store1, type: "action" }, - { type: "report-end", spyReportEnd: true } - ]) - - d() }) test("custom action decorator on field (babel)", function () { @@ -445,40 +379,9 @@ test("custom action decorator on field (babel)", function () { const store1 = new Store(2) const store2 = new Store(7) - const events = [] - const d = spy(events.push.bind(events)) expect(store1.add(3, 4)).toBe(14) expect(store2.add(5, 4)).toBe(63) expect(store1.add(2, 2)).toBe(8) - - expect(normalizeSpyEvents(events)).toEqual([ - { - arguments: [3, 4], - name: "zoem zoem", - spyReportStart: true, - object: store1, - type: "action" - }, - { type: "report-end", spyReportEnd: true }, - { - arguments: [5, 4], - name: "zoem zoem", - spyReportStart: true, - object: store2, - type: "action" - }, - { type: "report-end", spyReportEnd: true }, - { - arguments: [2, 2], - name: "zoem zoem", - spyReportStart: true, - object: store1, - type: "action" - }, - { type: "report-end", spyReportEnd: true } - ]) - - d() }) test("267 (babel) should be possible to declare properties observable outside strict mode", () => { diff --git a/packages/mobx/__tests__/base/extras.js b/packages/mobx/__tests__/base/extras.js index 528c1b87f..dc453717b 100644 --- a/packages/mobx/__tests__/base/extras.js +++ b/packages/mobx/__tests__/base/extras.js @@ -130,37 +130,6 @@ test("names", function () { expect(task[$mobx].values_.get("title").name_).toBe("Task@4.title") }) -function stripTrackerOutput(output) { - return output.map(function (i) { - if (Array.isArray(i)) return stripTrackerOutput(i) - delete i.object - delete i.time - delete i.fn - return i - }) -} - -test("spy 1", function () { - m._resetGlobalState() - const lines = [] - - const a = m.observable.box(3) - const b = m.computed(function () { - return a.get() * 2 - }) - m.autorun(function () { - b.get() - }) - const stop = m.spy(function (line) { - lines.push(line) - }) - - a.set(4) - stop() - a.set(5) - expect(stripTrackerOutput(lines)).toMatchSnapshot() -}) - test("get atom", function () { mobx._resetGlobalState() mobx._getGlobalState().mobxGuid = 0 // hmm dangerous reset? diff --git a/packages/mobx/__tests__/base/flow.js b/packages/mobx/__tests__/base/flow.js index 2676c4d79..2ab9a4b4b 100644 --- a/packages/mobx/__tests__/base/flow.js +++ b/packages/mobx/__tests__/base/flow.js @@ -147,38 +147,6 @@ test("it should support asyncAction in classes", done => { }, 10) }) -test("it should support logging", done => { - mobx.configure({ enforceActions: "observed" }) - const events = [] - const x = mobx.observable({ a: 1 }) - - const f = mobx.flow(function* myaction(initial) { - x.a = initial - x.a = yield delay(100, 5) - x.a = 4 - x.a = yield delay(100, 3) - return x.a - }) - const d = mobx.spy(ev => events.push(ev)) - - setTimeout(() => { - f(2).then(() => { - expect(stripEvents(events)).toMatchSnapshot() - d() - done() - }) - }, 10) -}) - -function stripEvents(events) { - return events.map(e => { - delete e.object - delete e.fn - delete e.time - return e - }) -} - test("flows are cancelled with an instance of FlowCancellationError", async () => { const start = flow(function* () { yield Promise.resolve() diff --git a/packages/mobx/__tests__/base/observables.js b/packages/mobx/__tests__/base/observables.js index b30ba4f64..cc067111e 100644 --- a/packages/mobx/__tests__/base/observables.js +++ b/packages/mobx/__tests__/base/observables.js @@ -862,15 +862,6 @@ test("when 2", function () { expect(d[$mobx].name_).toBe("when x is 3") }) -function stripSpyOutput(events) { - events.forEach(ev => { - delete ev.time - delete ev.fn - delete ev.object - }) - return events -} - test("issue 50", function (done) { m._resetGlobalState() mobx._getGlobalState().mobxGuid = 0 @@ -890,10 +881,6 @@ test("issue 50", function (done) { result = [x.a, x.b, x.c].join(",") }) - const disposer2 = mobx.spy(function (info) { - events.push(info) - }) - setTimeout(function () { mobx.transaction(function () { events.push("transstart") @@ -905,50 +892,11 @@ test("issue 50", function (done) { expect(result).toBe("false,true,true") expect(x.c).toBe(x.b) - expect(stripSpyOutput(events)).toMatchSnapshot() - disposer1() - disposer2() done() }, 500) }) -test("verify transaction events", function () { - m._resetGlobalState() - mobx._getGlobalState().mobxGuid = 0 - - const x = observable({ - b: 1, - get c() { - events.push("calc c") - return this.b - } - }) - - const events = [] - const disposer1 = mobx.autorun(function ar() { - events.push("auto") - x.c - }) - - const disposer2 = mobx.spy(function (info) { - events.push(info) - }) - - mobx.transaction(function () { - events.push("transstart") - x.b = 1 - x.b = 2 - events.push("transpreend") - }) - events.push("transpostend") - - expect(stripSpyOutput(events)).toMatchSnapshot() - - disposer1() - disposer2() -}) - test("verify array in transaction", function () { const ar = observable([]) let aCount = 0 @@ -982,9 +930,6 @@ test("delay autorun until end of transaction", function () { } }) let disposer1 - const disposer2 = mobx.spy(function (info) { - events.push(info) - }) let didRun = false mobx.transaction(function () { @@ -1014,10 +959,6 @@ test("delay autorun until end of transaction", function () { disposer1() x.a = 3 events.push("post trans3") - - expect(stripSpyOutput(events)).toMatchSnapshot() - - disposer2() }) test("prematurely end autorun", function () { diff --git a/packages/mobx/__tests__/base/spy.js b/packages/mobx/__tests__/base/spy.js deleted file mode 100644 index d837dc7c9..000000000 --- a/packages/mobx/__tests__/base/spy.js +++ /dev/null @@ -1,177 +0,0 @@ -"use strict" -const mobx = require("../../src/mobx.ts") -const utils = require("../utils/test-utils") - -test("spy output", () => { - const events = [] - - const stop = mobx.spy(c => events.push(c)) - - doStuff() - - stop() - - doStuff() - - events.forEach(ev => { - delete ev.object - delete ev.fn - delete ev.time - }) - - expect(events).toMatchSnapshot() -}) - -function doStuff() { - const a = mobx.observable.box(2) - a.set(3) - - const b = mobx.observable({ - c: 4 - }) - b.c = 5 - mobx.extendObservable(b, { d: 6 }) - b.d = 7 - - const e = mobx.observable([1, 2]) - e.push(3, 4) - e.shift() - e[2] = 5 - - const f = mobx.observable.map({ g: 1 }) - f.delete("h") - f.delete("g") - f.set("i", 5) - f.set("i", 6) - - const j = mobx.computed(() => a.get() * 2) - - mobx.autorun(() => { - j.get() - }) - - a.set(4) - - mobx.transaction(function myTransaction() { - a.set(5) - a.set(6) - }) - - mobx.action("myTestAction", newValue => { - a.set(newValue) - }).call({}, 7) -} - -test("spy error", () => { - utils.supressConsole(() => { - mobx._getGlobalState().mobxGuid = 0 - - const a = mobx.observable({ - x: 2, - get y() { - if (this.x === 3) throw "Oops" - return this.x * 2 - }, - setX: mobx.action(function setX(x) { - this.x = x - }) - }) - - const events = [] - const stop = mobx.spy(c => events.push(c)) - - const d = mobx.autorun(() => a.y, { name: "autorun" }) - - a.x = 3 - a.setX(4) - const actionEvents = events.filter(event => event.type === "action") - const isActionsTypeofObservable = actionEvents.reduce( - (ret, action) => ret && action.object === a, - true - ) - events.forEach(x => { - delete x.fn - delete x.object - delete x.time - }) - expect(isActionsTypeofObservable).toBe(true) - expect(events).toMatchSnapshot() - - d() - stop() - }) -}) - -test("spy stop listen from handler, #1459", () => { - const stop = mobx.spy(() => stop()) - mobx.spy(() => {}) - doStuff() -}) - -test("bound actions report correct object (discussions/3140)", () => { - class AppState { - constructor() { - mobx.makeAutoObservable( - this, - { - actionBound: mobx.actionBound - }, - { autoBind: true } - ) - } - - actionBound() {} - autoActionBound() {} - } - - const appState = new AppState() - const { actionBound, autoActionBound } = appState - - let events = [] - const disposeSpy = mobx.spy(event => { - if (event.type !== "action") return - events.push(event) - }) - - try { - actionBound() - expect(events.pop().object).toBe(appState) - autoActionBound() - expect(events.pop().object).toBe(appState) - } finally { - disposeSpy() - } -}) - -test("computed shouldn't report update unless the value changed #3109", () => { - const number = mobx.observable({ - value: 0, - get isEven() { - return this.value % 2 === 0 - } - }) - - const events = [] - const disposeSpy = mobx.spy(event => { - if (event.observableKind === "computed" && event.type === "update") { - events.push(event) - } - }) - - const disposeAutorun = mobx.autorun(() => number.isEven) - - try { - expect(events.pop()).toMatchObject({ oldValue: { cause: null }, newValue: true }) - number.value++ // 1 - expect(events.pop()).toMatchObject({ oldValue: true, newValue: false }) - number.value++ // 2 - expect(events.pop()).toMatchObject({ oldValue: false, newValue: true }) - number.value += 2 // 4 - expect(events.pop()).toBe(undefined) - number.value += 2 // 6 - expect(events.pop()).toBe(undefined) - } finally { - disposeSpy() - disposeAutorun() - } -}) diff --git a/packages/mobx/__tests__/base/stage3-decorators.ts b/packages/mobx/__tests__/base/stage3-decorators.ts index 7cd5b3d10..1a7fa78ab 100644 --- a/packages/mobx/__tests__/base/stage3-decorators.ts +++ b/packages/mobx/__tests__/base/stage3-decorators.ts @@ -13,7 +13,6 @@ import { isObservableObject, transaction, IObjectDidChange, - spy, configure, isAction, IAtom, @@ -356,14 +355,6 @@ test("issue 191 - shared initializers (2022.3)", () => { t.deepEqual(t2.array.slice(), [2, 4]) }) -function normalizeSpyEvents(events: any[]) { - events.forEach(ev => { - delete ev.fn - delete ev.time - }) - return events -} - test("action decorator (2022.3)", () => { class Store { constructor(private multiplier: number) {} @@ -376,22 +367,9 @@ test("action decorator (2022.3)", () => { const store1 = new Store(2) const store2 = new Store(3) - const events: any[] = [] - const d = spy(events.push.bind(events)) t.equal(store1.add(3, 4), 14) t.equal(store2.add(2, 2), 12) t.equal(store1.add(1, 1), 4) - - t.deepEqual(normalizeSpyEvents(events), [ - { arguments: [3, 4], name: "add", spyReportStart: true, object: store1, type: "action" }, - { type: "report-end", spyReportEnd: true }, - { arguments: [2, 2], name: "add", spyReportStart: true, object: store2, type: "action" }, - { type: "report-end", spyReportEnd: true }, - { arguments: [1, 1], name: "add", spyReportStart: true, object: store1, type: "action" }, - { type: "report-end", spyReportEnd: true } - ]) - - d() }) test("custom action decorator (2022.3)", () => { @@ -406,40 +384,9 @@ test("custom action decorator (2022.3)", () => { const store1 = new Store(2) const store2 = new Store(3) - const events: any[] = [] - const d = spy(events.push.bind(events)) t.equal(store1.add(3, 4), 14) t.equal(store2.add(2, 2), 12) t.equal(store1.add(1, 1), 4) - - t.deepEqual(normalizeSpyEvents(events), [ - { - arguments: [3, 4], - name: "zoem zoem", - spyReportStart: true, - object: store1, - type: "action" - }, - { type: "report-end", spyReportEnd: true }, - { - arguments: [2, 2], - name: "zoem zoem", - spyReportStart: true, - object: store2, - type: "action" - }, - { type: "report-end", spyReportEnd: true }, - { - arguments: [1, 1], - name: "zoem zoem", - spyReportStart: true, - object: store1, - type: "action" - }, - { type: "report-end", spyReportEnd: true } - ]) - - d() }) test("action decorator on field (2022.3)", () => { @@ -456,22 +403,9 @@ test("action decorator on field (2022.3)", () => { const store2 = new Store(7) expect(store1.add).not.toEqual(store2.add) - const events: any[] = [] - const d = spy(events.push.bind(events)) t.equal(store1.add(3, 4), 14) t.equal(store2.add(4, 5), 63) t.equal(store1.add(2, 2), 8) - - t.deepEqual(normalizeSpyEvents(events), [ - { arguments: [3, 4], name: "add", spyReportStart: true, object: store1, type: "action" }, - { type: "report-end", spyReportEnd: true }, - { arguments: [4, 5], name: "add", spyReportStart: true, object: store2, type: "action" }, - { type: "report-end", spyReportEnd: true }, - { arguments: [2, 2], name: "add", spyReportStart: true, object: store1, type: "action" }, - { type: "report-end", spyReportEnd: true } - ]) - - d() }) test("custom action decorator on field (2022.3)", () => { @@ -487,40 +421,9 @@ test("custom action decorator on field (2022.3)", () => { const store1 = new Store(2) const store2 = new Store(7) - const events: any[] = [] - const d = spy(events.push.bind(events)) t.equal(store1.add(3, 4), 14) t.equal(store2.add(4, 5), 63) t.equal(store1.add(2, 2), 8) - - t.deepEqual(normalizeSpyEvents(events), [ - { - arguments: [3, 4], - name: "zoem zoem", - spyReportStart: true, - object: store1, - type: "action" - }, - { type: "report-end", spyReportEnd: true }, - { - arguments: [4, 5], - name: "zoem zoem", - spyReportStart: true, - object: store2, - type: "action" - }, - { type: "report-end", spyReportEnd: true }, - { - arguments: [2, 2], - name: "zoem zoem", - spyReportStart: true, - object: store1, - type: "action" - }, - { type: "report-end", spyReportEnd: true } - ]) - - d() }) test("267 (2022.3) should be possible to declare properties observable outside strict mode", () => { diff --git a/packages/mobx/__tests__/base/typescript-tests.ts b/packages/mobx/__tests__/base/typescript-tests.ts index 245fb27a9..d25161913 100644 --- a/packages/mobx/__tests__/base/typescript-tests.ts +++ b/packages/mobx/__tests__/base/typescript-tests.ts @@ -23,7 +23,6 @@ import { isObservableObject, transaction, IObjectDidChange, - spy, configure, isAction, makeObservable, @@ -505,14 +504,6 @@ test("issue 191 - shared initializers (ts)", () => { t.deepEqual(t2.array.slice(), [2, 4]) }) -function normalizeSpyEvents(events: any[]) { - events.forEach(ev => { - delete ev.fn - delete ev.time - }) - return events -} - test("action decorator (typescript)", () => { class Store { constructor(private multiplier: number) { @@ -528,22 +519,9 @@ test("action decorator (typescript)", () => { const store1 = new Store(2) const store2 = new Store(3) - const events: any[] = [] - const d = spy(events.push.bind(events)) t.equal(store1.add(3, 4), 14) t.equal(store2.add(2, 2), 12) t.equal(store1.add(1, 1), 4) - - t.deepEqual(normalizeSpyEvents(events), [ - { arguments: [3, 4], name: "add", spyReportStart: true, object: store1, type: "action" }, - { type: "report-end", spyReportEnd: true }, - { arguments: [2, 2], name: "add", spyReportStart: true, object: store2, type: "action" }, - { type: "report-end", spyReportEnd: true }, - { arguments: [1, 1], name: "add", spyReportStart: true, object: store1, type: "action" }, - { type: "report-end", spyReportEnd: true } - ]) - - d() }) test("custom action decorator (typescript)", () => { @@ -561,40 +539,9 @@ test("custom action decorator (typescript)", () => { const store1 = new Store(2) const store2 = new Store(3) - const events: any[] = [] - const d = spy(events.push.bind(events)) t.equal(store1.add(3, 4), 14) t.equal(store2.add(2, 2), 12) t.equal(store1.add(1, 1), 4) - - t.deepEqual(normalizeSpyEvents(events), [ - { - arguments: [3, 4], - name: "zoem zoem", - spyReportStart: true, - object: store1, - type: "action" - }, - { type: "report-end", spyReportEnd: true }, - { - arguments: [2, 2], - name: "zoem zoem", - spyReportStart: true, - object: store2, - type: "action" - }, - { type: "report-end", spyReportEnd: true }, - { - arguments: [1, 1], - name: "zoem zoem", - spyReportStart: true, - object: store1, - type: "action" - }, - { type: "report-end", spyReportEnd: true } - ]) - - d() }) test("action decorator on field (typescript)", () => { @@ -614,22 +561,9 @@ test("action decorator on field (typescript)", () => { const store2 = new Store(7) expect(store1.add).not.toEqual(store2.add) - const events: any[] = [] - const d = spy(events.push.bind(events)) t.equal(store1.add(3, 4), 14) t.equal(store2.add(4, 5), 63) t.equal(store1.add(2, 2), 8) - - t.deepEqual(normalizeSpyEvents(events), [ - { arguments: [3, 4], name: "add", spyReportStart: true, object: store1, type: "action" }, - { type: "report-end", spyReportEnd: true }, - { arguments: [4, 5], name: "add", spyReportStart: true, object: store2, type: "action" }, - { type: "report-end", spyReportEnd: true }, - { arguments: [2, 2], name: "add", spyReportStart: true, object: store1, type: "action" }, - { type: "report-end", spyReportEnd: true } - ]) - - d() }) test("custom action decorator on field (typescript)", () => { @@ -648,40 +582,9 @@ test("custom action decorator on field (typescript)", () => { const store1 = new Store(2) const store2 = new Store(7) - const events: any[] = [] - const d = spy(events.push.bind(events)) t.equal(store1.add(3, 4), 14) t.equal(store2.add(4, 5), 63) t.equal(store1.add(2, 2), 8) - - t.deepEqual(normalizeSpyEvents(events), [ - { - arguments: [3, 4], - name: "zoem zoem", - spyReportStart: true, - object: store1, - type: "action" - }, - { type: "report-end", spyReportEnd: true }, - { - arguments: [4, 5], - name: "zoem zoem", - spyReportStart: true, - object: store2, - type: "action" - }, - { type: "report-end", spyReportEnd: true }, - { - arguments: [2, 2], - name: "zoem zoem", - spyReportStart: true, - object: store1, - type: "action" - }, - { type: "report-end", spyReportEnd: true } - ]) - - d() }) test("267 (typescript) should be possible to declare properties observable outside strict mode", () => { diff --git a/packages/mobx/src/api/observable.ts b/packages/mobx/src/api/observable.ts index eaa68e1bd..b96e0dfef 100644 --- a/packages/mobx/src/api/observable.ts +++ b/packages/mobx/src/api/observable.ts @@ -187,7 +187,7 @@ export interface IObservableFactory extends Annotation, ClassAccessorAndFieldDec const observableFactories: IObservableFactory = { box(value: T, options?: CreateObservableOptions): IObservableValue { const o = asCreateObservableOptions(options) - return new ObservableValue(value, getEnhancerFromOptions(o), o.name, true, o.equals) + return new ObservableValue(value, getEnhancerFromOptions(o), o.name, o.equals) }, array(initialValues?: T[], options?: CreateObservableOptions): IObservableArray { const o = asCreateObservableOptions(options) diff --git a/packages/mobx/src/core/action.ts b/packages/mobx/src/core/action.ts index 50dd708fa..23c5e4032 100644 --- a/packages/mobx/src/core/action.ts +++ b/packages/mobx/src/core/action.ts @@ -2,17 +2,12 @@ import { IDerivation, endBatch, globalState, - isSpyEnabled, - spyReportEnd, - spyReportStart, startBatch, untrackedEnd, untrackedStart, isFunction, allowStateReadsStart, allowStateReadsEnd, - ACTION, - EMPTY_ARRAY, die, getDescriptor, defineProperty @@ -80,8 +75,6 @@ export interface IActionRunInfo { prevDerivation_: IDerivation | null prevAllowStateChanges_: boolean prevAllowStateReads_: boolean - notifySpy_: boolean - startTime_: number error_?: any parentActionId_: number actionId_: number @@ -94,18 +87,6 @@ export function _startAction( scope: any, args?: IArguments ): IActionRunInfo { - const notifySpy_ = __DEV__ && isSpyEnabled() && !!actionName - let startTime_: number = 0 - if (notifySpy_) { - startTime_ = Date.now() - const flattenedArgs = args ? Array.from(args) : EMPTY_ARRAY - spyReportStart({ - type: ACTION, - name: actionName, - object: scope, - arguments: flattenedArgs - }) - } const prevDerivation_ = globalState.trackingDerivation const runAsAction = !canRunAsDerivation || !prevDerivation_ startBatch() @@ -125,8 +106,6 @@ export function _startAction( prevDerivation_, prevAllowStateChanges_, prevAllowStateReads_, - notifySpy_, - startTime_, actionId_: nextActionId++, parentActionId_: currentActionId } @@ -151,9 +130,6 @@ export function _endAction(runInfo: IActionRunInfo) { if (runInfo.runAsAction_) { untrackedEnd(runInfo.prevDerivation_) } - if (__DEV__ && runInfo.notifySpy_) { - spyReportEnd({ time: Date.now() - runInfo.startTime_ }) - } globalState.suppressReactionErrors = false } diff --git a/packages/mobx/src/core/computedvalue.ts b/packages/mobx/src/core/computedvalue.ts index d570d7955..431e17738 100644 --- a/packages/mobx/src/core/computedvalue.ts +++ b/packages/mobx/src/core/computedvalue.ts @@ -13,12 +13,10 @@ import { getNextId, globalState, isCaughtException, - isSpyEnabled, propagateChangeConfirmed, propagateMaybeChanged, reportObserved, shouldCompute, - spyReport, startBatch, toPrimitive, trackDerivedFunction, @@ -44,15 +42,6 @@ export interface IComputedValueOptions { keepAlive?: boolean } -export type IComputedDidChange = { - type: "update" - observableKind: "computed" - object: unknown - debugObjectName: string - newValue: T - oldValue: T | undefined -} - /** * A node in the state dependency root that observes other nodes, and can be observed itself. * @@ -261,17 +250,6 @@ export class ComputedValue implements IObservable, IComputedValue, IDeriva if (changed) { this.value_ = newValue - - if (__DEV__ && isSpyEnabled()) { - spyReport({ - observableKind: "computed", - debugObjectName: this.name_, - object: this.scope_, - type: "update", - oldValue, - newValue - } as IComputedDidChange) - } } return changed diff --git a/packages/mobx/src/core/globalstate.ts b/packages/mobx/src/core/globalstate.ts index a1c4de647..e72e83149 100644 --- a/packages/mobx/src/core/globalstate.ts +++ b/packages/mobx/src/core/globalstate.ts @@ -1,14 +1,13 @@ import { IDerivation, IObservable, Reaction, die } from "../internal" import { ComputedValue } from "./computedvalue" -const MOBX_GLOBALS_VERSION = 7 +const MOBX_GLOBALS_VERSION = 8 /** * These values will persist if global state is reset */ const persistentKeys: (keyof MobXGlobals)[] = [ "mobxGuid", - "spyListeners", "enforceActions", "computedRequiresReaction", "reactionRequiresObservable", @@ -100,11 +99,6 @@ export class MobXGlobals { */ enforceActions: boolean | "always" = true - /** - * Spy callbacks - */ - spyListeners: { (change: any): void }[] = [] - /** * Globally attached error handlers that react specifically to errors in reactions */ diff --git a/packages/mobx/src/core/reaction.ts b/packages/mobx/src/core/reaction.ts index 21382c85b..fa72ebd90 100644 --- a/packages/mobx/src/core/reaction.ts +++ b/packages/mobx/src/core/reaction.ts @@ -10,11 +10,7 @@ import { getNextId, globalState, isCaughtException, - isSpyEnabled, shouldCompute, - spyReport, - spyReportEnd, - spyReportStart, startBatch, trackDerivedFunction, GenericAbortSignal @@ -135,13 +131,6 @@ export class Reaction implements IDerivation, IReactionPublic { try { this.onInvalidate_() - if (__DEV__ && this.isTrackPending && isSpyEnabled()) { - // onInvalidate didn't trigger track right away.. - spyReport({ - name: this.name_, - type: "scheduled-reaction" - }) - } } catch (e) { this.reportExceptionInDerivation_(e) } @@ -157,15 +146,6 @@ export class Reaction implements IDerivation, IReactionPublic { // console.warn("Reaction already disposed") // Note: Not a warning / error in mobx 4 either } startBatch() - const notify = __DEV__ && isSpyEnabled() - let startTime - if (__DEV__ && notify) { - startTime = Date.now() - spyReportStart({ - name: this.name_, - type: "reaction" - }) - } this.isRunning = true const prevReaction = globalState.trackingContext // reactions could create reactions... globalState.trackingContext = this @@ -180,11 +160,6 @@ export class Reaction implements IDerivation, IReactionPublic { if (isCaughtException(result)) { this.reportExceptionInDerivation_(result.cause) } - if (__DEV__ && notify) { - spyReportEnd({ - time: Date.now() - startTime - }) - } endBatch() } @@ -206,15 +181,6 @@ export class Reaction implements IDerivation, IReactionPublic { /** If debugging brought you here, please, read the above message :-). Tnx! */ } else if (__DEV__) { console.warn(`[mobx] (error in reaction '${this.name_}' suppressed, fix error of causing action below)`) } // prettier-ignore - if (__DEV__ && isSpyEnabled()) { - spyReport({ - type: "error", - name: this.name_, - message, - error: "" + error - }) - } - globalState.globalReactionErrorHandlers.forEach(f => f(error, this)) } diff --git a/packages/mobx/src/core/spy.ts b/packages/mobx/src/core/spy.ts deleted file mode 100644 index 7344ffe01..000000000 --- a/packages/mobx/src/core/spy.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { IComputedDidChange } from "./computedvalue" -import { IValueDidChange, IBoxDidChange } from "./../types/observablevalue" -import { IObjectDidChange } from "./../types/observableobject" -import { IArrayDidChange } from "./../types/observablearray" -import { Lambda, globalState, once, ISetDidChange, IMapDidChange, assign } from "../internal" - -export function isSpyEnabled() { - return __DEV__ && !!globalState.spyListeners.length -} - -export type PureSpyEvent = - | { type: "action"; name: string; object: unknown; arguments: unknown[] } - | { type: "scheduled-reaction"; name: string } - | { type: "reaction"; name: string } - | { type: "error"; name: string; message: string; error: string } - | IComputedDidChange - | IObjectDidChange - | IArrayDidChange - | IMapDidChange - | ISetDidChange - | IValueDidChange - | IBoxDidChange - | { type: "report-end"; spyReportEnd: true; time?: number } - -type SpyEvent = PureSpyEvent & { spyReportStart?: true } - -export function spyReport(event: SpyEvent) { - if (!__DEV__) { - return - } // dead code elimination can do the rest - if (!globalState.spyListeners.length) { - return - } - const listeners = globalState.spyListeners - for (let i = 0, l = listeners.length; i < l; i++) { - listeners[i](event) - } -} - -export function spyReportStart(event: PureSpyEvent) { - if (!__DEV__) { - return - } - const change = assign({}, event, { spyReportStart: true as const }) - spyReport(change) -} - -const END_EVENT: SpyEvent = { type: "report-end", spyReportEnd: true } - -export function spyReportEnd(change?: { time?: number }) { - if (!__DEV__) { - return - } - if (change) { - spyReport(assign({}, change, { type: "report-end" as const, spyReportEnd: true as const })) - } else { - spyReport(END_EVENT) - } -} - -export function spy(listener: (change: SpyEvent) => void): Lambda { - if (!__DEV__) { - console.warn(`[mobx.spy] Is a no-op in production builds`) - return function () {} - } else { - globalState.spyListeners.push(listener) - return once(() => { - globalState.spyListeners = globalState.spyListeners.filter(l => l !== listener) - }) - } -} diff --git a/packages/mobx/src/internal.ts b/packages/mobx/src/internal.ts index 0ab576def..fe5f692d9 100644 --- a/packages/mobx/src/internal.ts +++ b/packages/mobx/src/internal.ts @@ -27,7 +27,6 @@ export * from "./core/derivation" export * from "./core/globalstate" export * from "./core/observable" export * from "./core/reaction" -export * from "./core/spy" export * from "./api/action" export * from "./api/autorun" export * from "./api/become-observed" diff --git a/packages/mobx/src/mobx.ts b/packages/mobx/src/mobx.ts index d3cd161c5..11a27147a 100644 --- a/packages/mobx/src/mobx.ts +++ b/packages/mobx/src/mobx.ts @@ -25,8 +25,6 @@ if (__DEV__) { }) } -import { spy, getDebugName, $mobx } from "./internal" - export { IObservable, IDepTreeNode, @@ -36,7 +34,6 @@ export { untracked, IAtom, createAtom, - spy, IComputedValue, IEqualsComparer, compareDefault, @@ -153,16 +150,3 @@ export { AnnotationMapEntry, override } from "./internal" - -// Devtools support -declare const __MOBX_DEVTOOLS_GLOBAL_HOOK__: { injectMobx: (any) => void } -if (__DEV__ && typeof __MOBX_DEVTOOLS_GLOBAL_HOOK__ === "object") { - // See: https://github.com/andykog/mobx-devtools/ - __MOBX_DEVTOOLS_GLOBAL_HOOK__.injectMobx({ - spy, - extras: { - getDebugName - }, - $mobx - }) -} diff --git a/packages/mobx/src/types/observableannotation.ts b/packages/mobx/src/types/observableannotation.ts index 4c688d5f3..4dd1de9bf 100644 --- a/packages/mobx/src/types/observableannotation.ts +++ b/packages/mobx/src/types/observableannotation.ts @@ -80,8 +80,7 @@ export function decorateObservable20223_( ann.options_?.enhancer_ ?? deepEnhancer, __DEV__ ? `${adm.name_}.${name.toString()}` - : `ObservableObject.${name.toString()}`, - false + : `ObservableObject.${name.toString()}` ) ) return adm diff --git a/packages/mobx/src/types/observablearray.ts b/packages/mobx/src/types/observablearray.ts index c8ba985b7..0db90c8d8 100644 --- a/packages/mobx/src/types/observablearray.ts +++ b/packages/mobx/src/types/observablearray.ts @@ -14,10 +14,7 @@ import { hasListeners, interceptChange, isObject, - isSpyEnabled, notifyListeners, - spyReportEnd, - spyReportStart, hasProp, die, globalState, @@ -246,64 +243,46 @@ export class ObservableArrayAdministration } notifyArrayChildUpdate_(index: number, newValue: any, oldValue: any) { - const notifySpy = __DEV__ && !this.owned_ && isSpyEnabled() const notify = hasListeners(this) - const change: IArrayDidChange | null = - notify || notifySpy - ? ({ - observableKind: "array", - object: this.proxy_, - type: UPDATE, - debugObjectName: this.atom_.name_, - index, - newValue, - oldValue - } as const) - : null - - // The reason why this is on right hand side here (and not above), is this way the uglifier will drop it, but it won't - // cause any runtime overhead in development mode without NODE_ENV set, unless spying is enabled - if (__DEV__ && notifySpy) { - spyReportStart(change!) - } + const change: IArrayDidChange | null = notify + ? ({ + observableKind: "array", + object: this.proxy_, + type: UPDATE, + debugObjectName: this.atom_.name_, + index, + newValue, + oldValue + } as const) + : null + this.atom_.reportChanged() if (notify) { notifyListeners(this, change) } - if (__DEV__ && notifySpy) { - spyReportEnd() - } } notifyArraySplice_(index: number, added: any[], removed: any[]) { - const notifySpy = __DEV__ && !this.owned_ && isSpyEnabled() const notify = hasListeners(this) - const change: IArraySplice | null = - notify || notifySpy - ? ({ - observableKind: "array", - object: this.proxy_, - debugObjectName: this.atom_.name_, - type: SPLICE, - index, - removed, - added, - removedCount: removed.length, - addedCount: added.length - } as const) - : null - - if (__DEV__ && notifySpy) { - spyReportStart(change!) - } + const change: IArraySplice | null = notify + ? ({ + observableKind: "array", + object: this.proxy_, + debugObjectName: this.atom_.name_, + type: SPLICE, + index, + removed, + added, + removedCount: removed.length, + addedCount: added.length + } as const) + : null + this.atom_.reportChanged() // conform: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/observe if (notify) { notifyListeners(this, change) } - if (__DEV__ && notifySpy) { - spyReportEnd() - } } get_(index: number): any | undefined { diff --git a/packages/mobx/src/types/observablemap.ts b/packages/mobx/src/types/observablemap.ts index 068ced100..aebb722b3 100644 --- a/packages/mobx/src/types/observablemap.ts +++ b/packages/mobx/src/types/observablemap.ts @@ -17,11 +17,8 @@ import { isES6Map, isPlainES6Map, isPlainObject, - isSpyEnabled, notifyListeners, referenceEnhancer, - spyReportEnd, - spyReportStart, stringifyKey, transaction, untracked, @@ -29,7 +26,6 @@ import { die, UPDATE, IAtom, - PureSpyEvent, initObservable } from "../internal" @@ -124,8 +120,7 @@ export class ObservableMap const newEntry = (entry = new ObservableValue( this.has_(key), referenceEnhancer, - __DEV__ ? `${this.name_}.${stringifyKey(key)}?` : "ObservableMap.key?", - false + __DEV__ ? `${this.name_}.${stringifyKey(key)}?` : "ObservableMap.key?" )) this.hasMap_.set(key, newEntry) newEntry.onBUOL = new Set([() => this.hasMap_.delete(key)]) @@ -169,23 +164,18 @@ export class ObservableMap } } if (this.has_(key)) { - const notifySpy = __DEV__ && isSpyEnabled() const notify = hasListeners(this) - const change: IMapDidChange | null = - notify || notifySpy - ? { - observableKind: "map", - debugObjectName: this.name_, - type: DELETE, - object: this, - oldValue: (this.data_.get(key)).value_, - name: key - } - : null - - if (__DEV__ && notifySpy) { - spyReportStart(change! as PureSpyEvent) - } // TODO fix type + const change: IMapDidChange | null = notify + ? { + observableKind: "map", + debugObjectName: this.name_, + type: DELETE, + object: this, + oldValue: (this.data_.get(key)).value_, + name: key + } + : null + transaction(() => { this.keysAtom_.reportChanged() this.hasMap_.get(key)?.setNewValue_(false) @@ -196,9 +186,6 @@ export class ObservableMap if (notify) { notifyListeners(this, change) } - if (__DEV__ && notifySpy) { - spyReportEnd() - } return true } return false @@ -208,30 +195,22 @@ export class ObservableMap const observable = this.data_.get(key)! newValue = (observable as any).prepareNewValue_(newValue) as V if (newValue !== globalState.UNCHANGED) { - const notifySpy = __DEV__ && isSpyEnabled() const notify = hasListeners(this) - const change: IMapDidChange | null = - notify || notifySpy - ? { - observableKind: "map", - debugObjectName: this.name_, - type: UPDATE, - object: this, - oldValue: (observable as any).value_, - name: key, - newValue - } - : null - if (__DEV__ && notifySpy) { - spyReportStart(change! as PureSpyEvent) - } // TODO fix type + const change: IMapDidChange | null = notify + ? { + observableKind: "map", + debugObjectName: this.name_, + type: UPDATE, + object: this, + oldValue: (observable as any).value_, + name: key, + newValue + } + : null observable.setNewValue_(newValue as V) if (notify) { notifyListeners(this, change) } - if (__DEV__ && notifySpy) { - spyReportEnd() - } } } @@ -241,36 +220,27 @@ export class ObservableMap const observable = new ObservableValue( newValue, this.enhancer_, - __DEV__ ? `${this.name_}.${stringifyKey(key)}` : "ObservableMap.key", - false + __DEV__ ? `${this.name_}.${stringifyKey(key)}` : "ObservableMap.key" ) this.data_.set(key, observable) newValue = (observable as any).value_ // value might have been changed this.hasMap_.get(key)?.setNewValue_(true) this.keysAtom_.reportChanged() }) - const notifySpy = __DEV__ && isSpyEnabled() const notify = hasListeners(this) - const change: IMapDidChange | null = - notify || notifySpy - ? { - observableKind: "map", - debugObjectName: this.name_, - type: ADD, - object: this, - name: key, - newValue - } - : null - if (__DEV__ && notifySpy) { - spyReportStart(change! as PureSpyEvent) - } // TODO fix type + const change: IMapDidChange | null = notify + ? { + observableKind: "map", + debugObjectName: this.name_, + type: ADD, + object: this, + name: key, + newValue + } + : null if (notify) { notifyListeners(this, change) } - if (__DEV__ && notifySpy) { - spyReportEnd() - } } get(key: K): V | undefined { diff --git a/packages/mobx/src/types/observableobject.ts b/packages/mobx/src/types/observableobject.ts index 455d360a1..c19300738 100644 --- a/packages/mobx/src/types/observableobject.ts +++ b/packages/mobx/src/types/observableobject.ts @@ -22,11 +22,8 @@ import { interceptChange, isObject, isPlainObject, - isSpyEnabled, notifyListeners, referenceEnhancer, - spyReportEnd, - spyReportStart, startBatch, stringifyKey, globalState, @@ -180,30 +177,22 @@ export class ObservableObjectAdministration // notify spy & observers if (newValue !== globalState.UNCHANGED) { const notify = hasListeners(this) - const notifySpy = __DEV__ && isSpyEnabled() - const change: IObjectDidChange | null = - notify || notifySpy - ? { - type: UPDATE, - observableKind: "object", - debugObjectName: this.name_, - object: this.proxy_ || this.target_, - oldValue: (observable as any).value_, - name: key, - newValue - } - : null - - if (__DEV__ && notifySpy) { - spyReportStart(change!) - } + const change: IObjectDidChange | null = notify + ? { + type: UPDATE, + observableKind: "object", + debugObjectName: this.name_, + object: this.proxy_ || this.target_, + oldValue: (observable as any).value_, + name: key, + newValue + } + : null + ;(observable as ObservableValue).setNewValue_(newValue) if (notify) { notifyListeners(this, change) } - if (__DEV__ && notifySpy) { - spyReportEnd() - } } return true } @@ -261,8 +250,7 @@ export class ObservableObjectAdministration entry = new ObservableValue( key in this.target_, referenceEnhancer, - __DEV__ ? `${this.name_}.${stringifyKey(key)}?` : "ObservableObject.key?", - false + __DEV__ ? `${this.name_}.${stringifyKey(key)}?` : "ObservableObject.key?" ) this.pendingKeys_.set(key, entry) } @@ -406,8 +394,7 @@ export class ObservableObjectAdministration const observable = new ObservableValue( value, enhancer, - __DEV__ ? `${this.name_}.${key.toString()}` : "ObservableObject.key", - false + __DEV__ ? `${this.name_}.${key.toString()}` : "ObservableObject.key" ) this.values_.set(key, observable) @@ -508,12 +495,11 @@ export class ObservableObjectAdministration try { startBatch() const notify = hasListeners(this) - const notifySpy = __DEV__ && isSpyEnabled() const observable = this.values_.get(key) - // Value needed for spies/listeners + // Value needed for listeners let value = undefined // Optimization: don't pull the value unless we will need it - if (!observable && (notify || notifySpy)) { + if (!observable && notify) { value = getDescriptor(this.target_, key)?.value } // delete prop (do first, may fail) @@ -545,8 +531,8 @@ export class ObservableObjectAdministration // "in" as it may still exist in proto this.pendingKeys_?.get(key)?.set(key in this.target_) - // Notify spies/listeners - if (notify || notifySpy) { + // Notify listeners + if (notify) { const change: IObjectDidChange = { type: REMOVE, observableKind: "object", @@ -555,15 +541,7 @@ export class ObservableObjectAdministration oldValue: value, name: key } - if (__DEV__ && notifySpy) { - spyReportStart(change!) - } - if (notify) { - notifyListeners(this, change) - } - if (__DEV__ && notifySpy) { - spyReportEnd() - } + notifyListeners(this, change) } } finally { endBatch() @@ -573,29 +551,16 @@ export class ObservableObjectAdministration notifyPropertyAddition_(key: PropertyKey, value: any) { const notify = hasListeners(this) - const notifySpy = __DEV__ && isSpyEnabled() - if (notify || notifySpy) { - const change: IObjectDidChange | null = - notify || notifySpy - ? ({ - type: ADD, - observableKind: "object", - debugObjectName: this.name_, - object: this.proxy_ || this.target_, - name: key, - newValue: value - } as const) - : null - - if (__DEV__ && notifySpy) { - spyReportStart(change!) - } - if (notify) { - notifyListeners(this, change) - } - if (__DEV__ && notifySpy) { - spyReportEnd() + if (notify) { + const change: IObjectDidChange = { + type: ADD, + observableKind: "object", + debugObjectName: this.name_, + object: this.proxy_ || this.target_, + name: key, + newValue: value } + notifyListeners(this, change) } this.pendingKeys_?.get(key)?.set(true) diff --git a/packages/mobx/src/types/observableset.ts b/packages/mobx/src/types/observableset.ts index ca088463b..b23899a73 100644 --- a/packages/mobx/src/types/observableset.ts +++ b/packages/mobx/src/types/observableset.ts @@ -4,12 +4,9 @@ import { deepEnhancer, getNextId, IEnhancer, - isSpyEnabled, hasListeners, IListenable, - spyReportStart, notifyListeners, - spyReportEnd, createInstanceofPredicate, makeIterable, hasInterceptors, @@ -130,27 +127,19 @@ export class ObservableSet implements Set, IInterceptable>{ - observableKind: "set", - debugObjectName: this.name_, - type: ADD, - object: this, - newValue: value - } - : null - if (notifySpy && __DEV__) { - spyReportStart(change!) - } + const change = notify + ? >{ + observableKind: "set", + debugObjectName: this.name_, + type: ADD, + object: this, + newValue: value + } + : null if (notify) { notifyListeners(this, change) } - if (notifySpy && __DEV__) { - spyReportEnd() - } } return this @@ -168,22 +157,17 @@ export class ObservableSet implements Set, IInterceptable>{ - observableKind: "set", - debugObjectName: this.name_, - type: DELETE, - object: this, - oldValue: value - } - : null - - if (notifySpy && __DEV__) { - spyReportStart(change!) - } + const change = notify + ? >{ + observableKind: "set", + debugObjectName: this.name_, + type: DELETE, + object: this, + oldValue: value + } + : null + transaction(() => { this.atom_.reportChanged() this.data_.delete(value) @@ -191,9 +175,6 @@ export class ObservableSet implements Set, IInterceptable = { newValue: T oldValue: T | undefined } -export type IBoxDidChange = - | { - type: "create" - observableKind: "value" - object: IObservableValue - debugObjectName: string - newValue: T - } - | IValueDidChange - export interface IObservableValue { get(): T set(value: T): void } -const CREATE = "create" - export class ObservableValue extends Atom implements IObservableValue, IInterceptable>, IListenable @@ -67,21 +51,10 @@ export class ObservableValue value: T, public enhancer_: IEnhancer, public name_ = __DEV__ ? "ObservableValue@" + getNextId() : "ObservableValue", - notifySpy = true, private equals_: IEqualsComparer = compareDefault ) { super(name_) this.value_ = enhancer_(value, undefined, name_) - if (__DEV__ && notifySpy && isSpyEnabled()) { - // only notify spy if this is a stand-alone observable - spyReport({ - type: CREATE, - object: this, - observableKind: "value", - debugObjectName: this.name_, - newValue: "" + this.value_?.toString() - }) - } } private dehanceValue(value: T): T { @@ -92,24 +65,9 @@ export class ObservableValue } public set(newValue: T) { - const oldValue = this.value_ newValue = this.prepareNewValue_(newValue) as any if (newValue !== globalState.UNCHANGED) { - const notifySpy = __DEV__ && isSpyEnabled() - if (__DEV__ && notifySpy) { - spyReportStart({ - type: UPDATE, - object: this, - observableKind: "value", - debugObjectName: this.name_, - newValue, - oldValue - }) - } this.setNewValue_(newValue) - if (__DEV__ && notifySpy) { - spyReportEnd() - } } } From af5f3bbdd624d5b3bf3e16482d848641373e0e93 Mon Sep 17 00:00:00 2001 From: Michel Weststrate Date: Thu, 30 Jul 2026 22:32:01 +0200 Subject: [PATCH 04/10] refactor(mobx): remove intercept API and interceptor machinery --- docs/api.md | 7 - .../base/__snapshots__/object-api.js.snap | 52 ---- packages/mobx/__tests__/base/api.js | 1 - packages/mobx/__tests__/base/intercept.js | 227 ------------------ packages/mobx/__tests__/base/map.js | 55 +---- packages/mobx/__tests__/base/object-api.js | 56 ----- packages/mobx/__tests__/base/set.js | 38 --- .../mobx/__tests__/base/stage3-decorators.ts | 2 - .../mobx/__tests__/base/typescript-tests.ts | 120 +-------- packages/mobx/src/api/intercept.ts | 60 ----- packages/mobx/src/errors.ts | 1 - packages/mobx/src/internal.ts | 2 - packages/mobx/src/mobx.ts | 9 - packages/mobx/src/types/intercept-utils.ts | 48 ---- packages/mobx/src/types/observablearray.ts | 50 +--- packages/mobx/src/types/observablemap.ts | 37 +-- packages/mobx/src/types/observableobject.ts | 97 +------- packages/mobx/src/types/observableset.ts | 42 +--- packages/mobx/src/types/observablevalue.ts | 26 +- 19 files changed, 9 insertions(+), 921 deletions(-) delete mode 100644 packages/mobx/__tests__/base/__snapshots__/object-api.js.snap delete mode 100644 packages/mobx/__tests__/base/intercept.js delete mode 100644 packages/mobx/src/api/intercept.ts delete mode 100644 packages/mobx/src/types/intercept-utils.ts diff --git a/docs/api.md b/docs/api.md index 4e7190b1d..9d9774f88 100644 --- a/docs/api.md +++ b/docs/api.md @@ -278,13 +278,6 @@ _Utilities that might make working with observable objects or computed values mo Attaches a global error listener, which is invoked for every error that is thrown from a _reaction_. This can be used for monitoring or test purposes. -### `intercept` - -{🚀} Usage: `intercept(propertyName|array|object|Set|Map, listener)` -([further information](intercept-and-observe.md#intercept)) - -Intercepts changes before they are applied to an observable API. Returns a disposer function that stops the interception. - ### `observe` {🚀} Usage: `observe(propertyName|array|object|Set|Map, listener)` diff --git a/packages/mobx/__tests__/base/__snapshots__/object-api.js.snap b/packages/mobx/__tests__/base/__snapshots__/object-api.js.snap deleted file mode 100644 index 1f032d429..000000000 --- a/packages/mobx/__tests__/base/__snapshots__/object-api.js.snap +++ /dev/null @@ -1,52 +0,0 @@ -// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing - -exports[`observe & intercept 1`] = ` -[ - { - "intercept": { - "name": "b", - "newValue": { - "title": "get tea", - }, - "object": "skip", - "type": "add", - }, - }, - { - "intercept": { - "name": "a", - "object": "skip", - "type": "remove", - }, - }, -] -`; - -exports[`observe & intercept 2`] = ` -[ - { - "observe": { - "debugObjectName": "TestObject", - "name": "b", - "newValue": { - "title": "get tea", - }, - "object": "skip", - "observableKind": "object", - "type": "add", - }, - }, - { - "observe": { - "debugObjectName": "TestObject", - "name": "a", - "object": "skip", - "observableKind": "object", - "oldValue": { - "title": "get coffee", - }, - "type": "remove", - }, - }, -] -`; diff --git a/packages/mobx/__tests__/base/api.js b/packages/mobx/__tests__/base/api.js index e248dc62c..a04db080f 100644 --- a/packages/mobx/__tests__/base/api.js +++ b/packages/mobx/__tests__/base/api.js @@ -41,7 +41,6 @@ test("correct api should be exposed", function () { "has", "_getGlobalState", "getObserverTree", - "intercept", "_interceptReads", "isAction", "isBoxedObservable", diff --git a/packages/mobx/__tests__/base/intercept.js b/packages/mobx/__tests__/base/intercept.js deleted file mode 100644 index 81cc6cc76..000000000 --- a/packages/mobx/__tests__/base/intercept.js +++ /dev/null @@ -1,227 +0,0 @@ -const m = require("../../src/mobx.ts") -const intercept = m.intercept - -test("intercept observable value", () => { - const a = m.observable.box(1) - - let d = intercept(a, () => { - return null - }) - - a.set(2) - - expect(a.get()).toBe(1) - - d() - - a.set(3) - expect(a.get()).toBe(3) - - d = intercept(a, c => { - expect(c.object).toBe(a) - if (c.newValue % 2 === 0) { - throw "value should be odd!" - } - return c - }) - - expect(() => { - a.set(4) - }).toThrow(/value should be odd/) - - expect(a.get()).toBe(3) - a.set(5) - expect(a.get()).toBe(5) - - d() - d = intercept(a, c => { - expect(c.object).toBe(a) - c.newValue *= 2 - return c - }) - - a.set(6) - expect(a.get()).toBe(12) - - intercept(a, c => { - expect(c.object).toBe(a) - c.newValue += 1 - return c - }) - - a.set(7) - expect(a.get()).toBe(15) - - d() - a.set(8) - expect(a.get()).toBe(9) -}) - -test("intercept array", () => { - const a = m.observable([1, 2]) - - let d = m.intercept(a, () => null) - a.push(2) - expect(a.slice()).toEqual([1, 2]) - - d() - - d = intercept(a, c => { - expect(c.object).toBe(a) - if (c.type === "splice") { - c.added.push(c.added[0] * 2) - c.removedCount = 1 - return c - } else if (c.type === "update") { - c.newValue = c.newValue * 3 - return c - } - }) - - a.unshift(3, 4) - - expect(a.slice()).toEqual([3, 4, 6, 2]) - a[2] = 5 - expect(a.slice()).toEqual([3, 4, 15, 2]) -}) - -test("intercept object", () => { - const a = m.observable({ - b: 3 - }) - - intercept(a, change => { - expect(change.object).toBe(a) - change.newValue *= 3 - return change - }) - - a.b = 4 - - expect(a.b).toBe(12) - - intercept(a, "b", change => { - change.newValue += 1 - return change - }) - - a.b = 5 - expect(a.b).toBe(16) - - const d3 = intercept(a, c => { - expect(c.name).toBe("b") - expect(c.object).toBe(a) - expect(c.type).toBe("update") - return null - }) - - a.b = 7 - expect(a.b).toBe(16) - - d3() - a.b = 7 - expect(a.b).toBe(22) -}) - -test("intercept property additions", () => { - const a = m.observable({}) - const d4 = intercept(a, change => { - expect(change.object).toBe(a) - if (change.type === "add") { - return null - } - return change - }) - - m.extendObservable(a, { c: 1 }) // not added! - expect(a.c).toBe(undefined) - expect(m.isObservableProp(a, "c")).toBe(false) - - d4() - - m.extendObservable(a, { c: 2 }) - expect(a.c).toBe(2) - expect(m.isObservableProp(a, "c")).toBe(true) -}) - -test("intercept map", () => { - const a = m.observable.map({ - b: 3 - }) - - intercept(a, c => { - expect(c.object).toBe(a) - c.newValue *= 3 - return c - }) - - a.set("b", 4) - - expect(a.get("b")).toBe(12) - - intercept(a, "b", c => { - c.newValue += 1 - return c - }) - - a.set("b", 5) - expect(a.get("b")).toBe(16) - - const d3 = intercept(a, c => { - expect(c.object).toBe(a) - expect(c.name).toBe("b"), expect(c.object).toBe(a) - expect(c.type).toBe("update") - return null - }) - - a.set("b", 7) - expect(a.get("b")).toBe(16) - - d3() - a.set("b", 7) - expect(a.get("b")).toBe(22) - - const d4 = intercept(a, c => { - expect(c.object).toBe(a) - if (c.type === "delete") return null - return c - }) - - a.delete("b") - expect(a.has("b")).toBe(true) - expect(a.get("b")).toBe(22) - - d4() - a.delete("b") - expect(a.has("b")).toBe(false) - expect(a.get("c")).toBe(undefined) -}) - -test("intercept prevent dispose from breaking current execution", () => { - const a = m.observable.box(1) - - intercept(a, c => { - c.newValue += 1 - return c - }) - - const d = intercept(a, c => { - d() - expect(c.object).toBe(a) - c.newValue *= 2 - return c - }) - - intercept(a, c => { - c.newValue += 1 - return c - }) - - a.set(2) - - expect(a.get()).toBe(7) - - a.set(2) - - expect(a.get()).toBe(4) -}) diff --git a/packages/mobx/__tests__/base/map.js b/packages/mobx/__tests__/base/map.js index dc4c294d2..c9e3c2f2e 100644 --- a/packages/mobx/__tests__/base/map.js +++ b/packages/mobx/__tests__/base/map.js @@ -1246,47 +1246,6 @@ test("noop mutations do NOT reportChanges", () => { expect(autorunInvocationCount).toBe(1) }) -test(".replace() calls and respects interceptors", () => { - const map = mobx.observable.map([ - [0, 0], - [1, 1], - [2, 2], - [3, 3] - ]) - const replacementMap = [ - [3, 33], - [4, 44], - [5, 55], - [0, 0] - ] - const expectedMap = [ - [2, 2], - [3, 3], - [5, 55], - [0, 0] - ] - - mobx.intercept(map, change => { - // cancel delete 2 - if (change.type === "delete" && change.name === 2) { - return null - } - // cancel update 3 - if (change.type === "update" && change.name === 3) { - return null - } - // cancel add 4 - if (change.type === "add" && change.name === 4) { - return null - } - return change - }) - - map.replace(replacementMap) - - expect(Array.from(map)).toEqual(expectedMap) -}) - test(".replace() should reportChanged on key order change", () => { const map = mobx.observable.map([ [1, 1], @@ -1299,24 +1258,12 @@ test(".replace() should reportChanged on key order change", () => { [2, 22] ] const expectedMap = [ - [1, 1], + [4, 44], [3, 33], [2, 22] ] let autorunInvocationCount = 0 - mobx.intercept(map, change => { - // cancel delete 1 - if (change.type === "delete" && change.name === 1) { - return null - } - // cancel add 4 - if (change.type === "add" && change.name === 4) { - return null - } - return change - }) - autorun(() => { autorunInvocationCount++ for (const _ of map.keys()) { diff --git a/packages/mobx/__tests__/base/object-api.js b/packages/mobx/__tests__/base/object-api.js index fe6203050..a98921c87 100644 --- a/packages/mobx/__tests__/base/object-api.js +++ b/packages/mobx/__tests__/base/object-api.js @@ -420,62 +420,6 @@ test("keys(array)", () => { expect(snapshots).toEqual([[0], [0, 1], [0, 1, 2, 3], [0, 1, 2, 3], [0, 1, 2], [0, 1]]) }) -test("observe & intercept", () => { - let events = [] - const todos = observable( - { - a: { title: "get coffee" } - }, - {}, - { - deep: false, - name: "TestObject" // stable name for snapshot - } - ) - mobx.observe(todos, c => { - events.push({ observe: { ...c, object: "skip" } }) - }) - const d = mobx.intercept(todos, c => { - events.push({ intercept: { ...c, object: "skip" } }) - return null // no addition! - }) - - set(todos, { b: { title: "get tea" } }) - remove(todos, "a") - expect(events).toMatchSnapshot() - expect(mobx.toJS(todos)).toEqual({ - a: { title: "get coffee" } - }) - - events.splice(0) - d() - set(todos, { b: { title: "get tea" } }) - remove(todos, "a") - expect(events).toMatchSnapshot() - expect(mobx.toJS(todos)).toEqual({ - b: { title: "get tea" } - }) -}) - -test("observe & intercept set called multiple times", () => { - const a = mobx.observable({}, {}, { name: "TestObject" }) // stable name for snapshot - const interceptLogs = [] - const observeLogs = [] - - mobx.intercept(a, change => { - interceptLogs.push(`${change.name}: ${change.newValue}`) - return change - }) - mobx.observe(a, change => observeLogs.push(`${change.name}: ${change.newValue}`)) - - mobx.set(a, "x", 0) - a.x = 1 - mobx.set(a, "x", 2) - - expect(interceptLogs).toEqual(["x: 0", "x: 1", "x: 2"]) - expect(observeLogs).toEqual(["x: 0", "x: 1", "x: 2"]) -}) - test("dynamically adding properties should preserve the original modifiers of an object", () => { const todos = observable.object( { diff --git a/packages/mobx/__tests__/base/set.js b/packages/mobx/__tests__/base/set.js index 408116f0b..9d109488a 100644 --- a/packages/mobx/__tests__/base/set.js +++ b/packages/mobx/__tests__/base/set.js @@ -474,41 +474,3 @@ describe("Observable Set methods are reactive", () => { expect(c).toBe(3) }) }) - -describe("Observable Set interceptors", () => { - let s = set() - - beforeEach(() => { - s = set() - }) - - test("Add does not add value if interceptor returned no change", () => { - mobx.intercept(s, change => { - if (change.type === "add" && change.newValue === 2) { - return undefined - } - - return change - }) - - s.add(1) - s.add(2) - - expect([...s]).toStrictEqual([1]) - }) - - test("Add respects newValue from interceptor", () => { - mobx.intercept(s, change => { - if (change.type === "add" && change.newValue === 2) { - change.newValue = 10 - } - - return change - }) - - s.add(1) - s.add(2) - - expect([...s]).toStrictEqual([1, 10]) - }) -}) diff --git a/packages/mobx/__tests__/base/stage3-decorators.ts b/packages/mobx/__tests__/base/stage3-decorators.ts index 1a7fa78ab..df5b1615c 100644 --- a/packages/mobx/__tests__/base/stage3-decorators.ts +++ b/packages/mobx/__tests__/base/stage3-decorators.ts @@ -5,8 +5,6 @@ import { autorun, extendObservable, IObservableArray, - IArrayWillChange, - IArrayWillSplice, IObservableValue, isObservable, isObservableProp, diff --git a/packages/mobx/__tests__/base/typescript-tests.ts b/packages/mobx/__tests__/base/typescript-tests.ts index d25161913..961db4308 100644 --- a/packages/mobx/__tests__/base/typescript-tests.ts +++ b/packages/mobx/__tests__/base/typescript-tests.ts @@ -13,10 +13,6 @@ import { action, actionBound, IArrayDidChange, - IArrayWillChange, - IArrayWillSplice, - IMapWillChange, - ISetWillChange, IObservableValue, isObservable, isObservableProp, @@ -33,9 +29,7 @@ import { IMapDidChange, IValueDidChange, ISetDidChange, - IValueWillChange, - flowResult, - IObjectWillChange + flowResult } from "../../src/mobx" import * as mobx from "../../src/mobx" import { assert, IsExact } from "conditional-type-checks" @@ -2051,118 +2045,6 @@ test("TS - type inference of Set", () => { set.delete("1") }) -test("TS - type inference of observe & intercept functions", () => { - const array = [1, 2] - const object = { numberKey: 1, stringKey: "string" } - const map = new Map([["testKey", 1]]) - const set = new Set([1]) - - const { regularArray, regularObject, regularMap, regularSet } = observable({ - regularArray: array, - regularObject: object, - regularMap: map, - regularSet: set - }) - - const observableArray = observable(array) - const observableObject = observable(object) - const observableMap = observable(map) - const observableSet = observable(set) - - // Array - mobx.observe(regularArray, argument => { - assert>>(true) - }) - mobx.intercept(regularArray, argument => { - assert | IArrayWillSplice>>(true) - return argument - }) - // ObservableArray - mobx.observe(observableArray, argument => { - assert>>(true) - }) - mobx.intercept(observableArray, argument => { - assert | IArrayWillSplice>>(true) - return argument - }) - // Object - mobx.observe(regularObject, argument => { - assert>(true) - }) - mobx.intercept(regularObject, argument => { - assert>(true) - return argument - }) - mobx.observe(regularObject, "numberKey", argument => { - assert>>(true) - }) - mobx.intercept(regularObject, "numberKey", argument => { - assert>>(true) - return argument - }) - // ObservableObject - mobx.observe(observableObject, argument => { - assert>(true) - }) - mobx.intercept(observableObject, argument => { - assert>(true) - return argument - }) - mobx.observe(observableObject, "numberKey", argument => { - assert>>(true) - }) - mobx.intercept(observableObject, "numberKey", argument => { - assert>>(true) - return argument - }) - // Map - mobx.observe(regularMap, argument => { - assert>>(true) - }) - mobx.intercept(regularMap, argument => { - assert>>(true) - return argument - }) - mobx.observe(regularMap, "testKey", argument => { - assert>>(true) - }) - mobx.intercept(regularMap, "testKey", argument => { - assert>>(true) - return argument - }) - // ObservableMap - mobx.observe(observableMap, argument => { - assert>>(true) - }) - mobx.intercept(observableMap, argument => { - assert>>(true) - return argument - }) - mobx.observe(observableMap, "testKey", argument => { - assert>>(true) - }) - mobx.intercept(observableMap, "testKey", argument => { - assert>>(true) - return argument - }) - // Set - mobx.observe(regularSet, argument => { - assert>>(true) - }) - mobx.intercept(regularSet, argument => { - assert>>(true) - return argument - }) - // ObservableSet - mobx.observe(observableSet, argument => { - assert>>(true) - }) - mobx.intercept(observableSet, argument => { - assert>>(true) - return argument - }) -}) - test("TS - type inference of reaction opts.equals", () => { const data = observable({ a: 23 }) mobx.reaction( diff --git a/packages/mobx/src/api/intercept.ts b/packages/mobx/src/api/intercept.ts deleted file mode 100644 index ee25998e9..000000000 --- a/packages/mobx/src/api/intercept.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { - IArrayWillChange, - IArrayWillSplice, - IInterceptor, - IMapWillChange, - IObjectWillChange, - IObservableArray, - IObservableValue, - IValueWillChange, - Lambda, - ObservableMap, - getAdministration, - ObservableSet, - ISetWillChange, - isFunction, - registerInterceptor -} from "../internal" - -export function intercept( - value: IObservableValue, - handler: IInterceptor> -): Lambda -export function intercept( - observableArray: IObservableArray | Array, - handler: IInterceptor | IArrayWillSplice> -): Lambda -export function intercept( - observableMap: ObservableMap | Map, - handler: IInterceptor> -): Lambda -export function intercept( - observableSet: ObservableSet | Set, - handler: IInterceptor> -): Lambda -export function intercept( - observableMap: ObservableMap | Map, - property: K, - handler: IInterceptor> -): Lambda -export function intercept(object: object, handler: IInterceptor): Lambda -export function intercept( - object: T, - property: K, - handler: IInterceptor> -): Lambda -export function intercept(thing, propOrHandler?, handler?): Lambda { - if (isFunction(handler)) { - return interceptProperty(thing, propOrHandler, handler) - } else { - return interceptInterceptable(thing, propOrHandler) - } -} - -function interceptInterceptable(thing, handler) { - return registerInterceptor(getAdministration(thing), handler) -} - -function interceptProperty(thing, property, handler) { - return registerInterceptor(getAdministration(thing, property), handler) -} diff --git a/packages/mobx/src/errors.ts b/packages/mobx/src/errors.ts index d642d6389..6333483ce 100644 --- a/packages/mobx/src/errors.ts +++ b/packages/mobx/src/errors.ts @@ -23,7 +23,6 @@ export const niceErrors = { 11: "'get()' can only be used on observable objects, arrays and maps", 12: `Invalid annotation`, 13: `Dynamic observable objects cannot be frozen. If you're passing observables to 3rd party component/function that calls Object.freeze, pass copy instead: toJS(observable)`, - 14: "Intercept handlers should return nothing or a change object", 15: `Observable arrays cannot be frozen. If you're passing observables to 3rd party component/function that calls Object.freeze, pass copy instead: toJS(observable)`, 16: `Modification exception: the internal structure of an observable array was changed.`, 19(other) { diff --git a/packages/mobx/src/internal.ts b/packages/mobx/src/internal.ts index fe5f692d9..37661e5c7 100644 --- a/packages/mobx/src/internal.ts +++ b/packages/mobx/src/internal.ts @@ -35,7 +35,6 @@ export * from "./api/extendobservable" export * from "./api/extras" export * from "./api/flow" export * from "./api/intercept-read" -export * from "./api/intercept" export * from "./api/iscomputed" export * from "./api/isobservable" export * from "./api/object-api" @@ -44,7 +43,6 @@ export * from "./api/tojs" export * from "./api/transaction" export * from "./api/when" export * from "./types/dynamicobject" -export * from "./types/intercept-utils" export * from "./types/listen-utils" export * from "./api/makeObservable" export * from "./types/observablearray" diff --git a/packages/mobx/src/mobx.ts b/packages/mobx/src/mobx.ts index 11a27147a..9f3ed30f5 100644 --- a/packages/mobx/src/mobx.ts +++ b/packages/mobx/src/mobx.ts @@ -41,19 +41,13 @@ export { compareStructural, compareShallow, IEnhancer, - IInterceptable, - IInterceptor, IListenable, - IObjectWillChange, IObjectDidChange, isObservableObject, IValueDidChange, - IValueWillChange, IObservableValue, isObservableValue as isBoxedObservable, IObservableArray, - IArrayWillChange, - IArrayWillSplice, IArraySplice, IArrayUpdate, IArrayDidChange, @@ -62,14 +56,12 @@ export { ObservableMap, IMapEntries, IMapEntry, - IMapWillChange, IMapDidChange, isObservableMap, IObservableMapInitialValues, ObservableSet, isObservableSet, ISetDidChange, - ISetWillChange, IObservableSetInitialValues, transaction, observable, @@ -88,7 +80,6 @@ export { isComputedProp, extendObservable, observe, - intercept, autorun, IAutorunOptions, reaction, diff --git a/packages/mobx/src/types/intercept-utils.ts b/packages/mobx/src/types/intercept-utils.ts deleted file mode 100644 index f7619e2ad..000000000 --- a/packages/mobx/src/types/intercept-utils.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { Lambda, once, untrackedEnd, untrackedStart, die } from "../internal" - -export type IInterceptor = (change: T) => T | null - -export interface IInterceptable { - interceptors_: IInterceptor[] | undefined -} - -export function hasInterceptors(interceptable: IInterceptable) { - return interceptable.interceptors_ !== undefined && interceptable.interceptors_.length > 0 -} - -export function registerInterceptor( - interceptable: IInterceptable, - handler: IInterceptor -): Lambda { - const interceptors = interceptable.interceptors_ || (interceptable.interceptors_ = []) - interceptors.push(handler) - return once(() => { - const idx = interceptors.indexOf(handler) - if (idx !== -1) { - interceptors.splice(idx, 1) - } - }) -} - -export function interceptChange( - interceptable: IInterceptable, - change: T | null -): T | null { - const prevU = untrackedStart() - try { - // Interceptor can modify the array, copy it to avoid concurrent modification, see #1950 - const interceptors = [...(interceptable.interceptors_ || [])] - for (let i = 0, l = interceptors.length; i < l; i++) { - change = interceptors[i](change) - if (change && !(change as any).type) { - die(14) - } - if (!change) { - break - } - } - return change - } finally { - untrackedEnd(prevU) - } -} diff --git a/packages/mobx/src/types/observablearray.ts b/packages/mobx/src/types/observablearray.ts index 0db90c8d8..7d96432f6 100644 --- a/packages/mobx/src/types/observablearray.ts +++ b/packages/mobx/src/types/observablearray.ts @@ -4,15 +4,12 @@ import { EMPTY_ARRAY, IAtom, IEnhancer, - IInterceptable, IListenable, addHiddenFinalProp, checkIfStateModificationsAreAllowed, createInstanceofPredicate, getNextId, - hasInterceptors, hasListeners, - interceptChange, isObject, notifyListeners, hasProp, @@ -56,21 +53,6 @@ export interface IArraySplice extends IArrayBaseChange { removedCount: number } -export interface IArrayWillChange { - object: IObservableArray - index: number - type: "update" - newValue: T -} - -export interface IArrayWillSplice { - object: IObservableArray - index: number - type: "splice" - added: T[] - removedCount: number -} - const arrayTraps = { get(target, name) { const adm: ObservableArrayAdministration = target[$mobx] @@ -106,12 +88,9 @@ const arrayTraps = { } } -export class ObservableArrayAdministration - implements IInterceptable | IArrayWillSplice>, IListenable -{ +export class ObservableArrayAdministration implements IListenable { atom_: IAtom readonly values_: any[] = [] // this is the prop that gets proxied, so can't replace it! - interceptors_ changeListeners_ enhancer_: (newV: any, oldV: any | undefined) => any dehancer: any @@ -193,21 +172,6 @@ export class ObservableArrayAdministration newItems = EMPTY_ARRAY } - if (hasInterceptors(this)) { - const change = interceptChange>(this as any, { - object: this.proxy_ as any, - type: SPLICE, - index, - removedCount: deleteCount, - added: newItems - }) - if (!change) { - return EMPTY_ARRAY - } - deleteCount = change.removedCount - newItems = change.added - } - newItems = newItems.length === 0 ? newItems : newItems.map(v => this.enhancer_(v, undefined)) if (__DEV__) { @@ -296,18 +260,6 @@ export class ObservableArrayAdministration // update at index in range checkIfStateModificationsAreAllowed(this.atom_) const oldValue = values[index] - if (hasInterceptors(this)) { - const change = interceptChange>(this as any, { - type: UPDATE, - object: this.proxy_ as any, // since "this" is the real array we need to pass its proxy - index, - newValue - }) - if (!change) { - return - } - newValue = change.newValue - } newValue = this.enhancer_(newValue, oldValue) const changed = newValue !== oldValue if (changed) { diff --git a/packages/mobx/src/types/observablemap.ts b/packages/mobx/src/types/observablemap.ts index aebb722b3..db9814b21 100644 --- a/packages/mobx/src/types/observablemap.ts +++ b/packages/mobx/src/types/observablemap.ts @@ -1,7 +1,6 @@ import { $mobx, IEnhancer, - IInterceptable, IListenable, ObservableValue, checkIfStateModificationsAreAllowed, @@ -11,9 +10,7 @@ import { deepEnhancer, getNextId, getPlainObjectKeys, - hasInterceptors, hasListeners, - interceptChange, isES6Map, isPlainES6Map, isPlainObject, @@ -60,13 +57,6 @@ export type IMapDidChange = { observableKind: "map"; debugObje } ) -export interface IMapWillChange { - object: ObservableMap - type: "update" | "add" | "delete" - name: K - newValue?: V -} - const ObservableMapMarker = {} export const ADD = "add" @@ -80,14 +70,11 @@ export type IObservableMapInitialValues = // just extend Map? See also https://gist.github.com/nestharus/13b4d74f2ef4a2f4357dbd3fc23c1e54 // But: https://github.com/mobxjs/mobx/issues/1556 -export class ObservableMap - implements Map, IInterceptable>, IListenable -{ +export class ObservableMap implements Map, IListenable { [$mobx] = ObservableMapMarker data_!: Map> hasMap_!: Map> // hasMap, not hashMap >-). keysAtom_!: IAtom - interceptors_ changeListeners_ dehancer: any @@ -131,18 +118,6 @@ export class ObservableMap set(key: K, value: V) { const hasKey = this.has_(key) - if (hasInterceptors(this)) { - const change = interceptChange>(this, { - type: hasKey ? UPDATE : ADD, - object: this, - newValue: value, - name: key - }) - if (!change) { - return this - } - value = change.newValue! - } if (hasKey) { this.updateValue_(key, value) } else { @@ -153,16 +128,6 @@ export class ObservableMap delete(key: K): boolean { checkIfStateModificationsAreAllowed(this.keysAtom_) - if (hasInterceptors(this)) { - const change = interceptChange>(this, { - type: DELETE, - object: this, - name: key - }) - if (!change) { - return false - } - } if (this.has_(key)) { const notify = hasListeners(this) const change: IMapDidChange | null = notify diff --git a/packages/mobx/src/types/observableobject.ts b/packages/mobx/src/types/observableobject.ts index c19300738..474a812d8 100644 --- a/packages/mobx/src/types/observableobject.ts +++ b/packages/mobx/src/types/observableobject.ts @@ -10,16 +10,13 @@ import { IAtom, IComputedValueOptions, IEnhancer, - IInterceptable, IListenable, ObservableValue, addHiddenProp, createInstanceofPredicate, endBatch, getNextId, - hasInterceptors, hasListeners, - interceptChange, isObject, isPlainObject, notifyListeners, @@ -38,8 +35,7 @@ import { autoAnnotation, getAdministration, getDebugName, - checkIfStateModificationsAreAllowed, - assign + checkIfStateModificationsAreAllowed } from "../internal" const descriptorCache = Object.create(null) @@ -65,27 +61,11 @@ export type IObjectDidChange = { } ) -export type IObjectWillChange = - | { - object: T - type: "update" | "add" - name: PropertyKey - newValue: any - } - | { - object: T - type: "remove" - name: PropertyKey - } - const REMOVE = "remove" -export class ObservableObjectAdministration - implements IInterceptable, IListenable -{ +export class ObservableObjectAdministration implements IListenable { keysAtom_: IAtom changeListeners_ - interceptors_ proxy_: any isPlainObject_: boolean appliedAnnotations_?: object @@ -159,22 +139,9 @@ export class ObservableObjectAdministration return true } - // intercept - if (hasInterceptors(this)) { - const change = interceptChange(this, { - type: UPDATE, - object: this.proxy_ || this.target_, - name: key, - newValue - }) - if (!change) { - return null - } - newValue = (change as any).newValue - } newValue = (observable as any).prepareNewValue_(newValue) - // notify spy & observers + // notify observers if (newValue !== globalState.UNCHANGED) { const notify = hasListeners(this) const change: IObjectDidChange | null = notify @@ -306,25 +273,6 @@ export class ObservableObjectAdministration return deleteOutcome } - // ADD interceptor - if (hasInterceptors(this)) { - const change = interceptChange(this, { - object: this.proxy_ || this.target_, - name: key, - type: ADD, - newValue: descriptor.value - }) - if (!change) { - return null - } - const { newValue } = change as any - if (descriptor.value !== newValue) { - descriptor = assign({}, descriptor, { - value: newValue - }) - } - } - // Define if (proxyTrap) { if (!Reflect.defineProperty(this.target_, key, descriptor)) { @@ -360,20 +308,6 @@ export class ObservableObjectAdministration return deleteOutcome } - // ADD interceptor - if (hasInterceptors(this)) { - const change = interceptChange(this, { - object: this.proxy_ || this.target_, - name: key, - type: ADD, - newValue: value - }) - if (!change) { - return null - } - value = (change as any).newValue - } - const cachedDescriptor = getCachedObservablePropDescriptor(key) const descriptor = { configurable: globalState.safeDescriptors ? this.isPlainObject_ : true, @@ -424,18 +358,6 @@ export class ObservableObjectAdministration return deleteOutcome } - // ADD interceptor - if (hasInterceptors(this)) { - const change = interceptChange(this, { - object: this.proxy_ || this.target_, - name: key, - type: ADD, - newValue: undefined - }) - if (!change) { - return null - } - } options.name ||= __DEV__ ? `${this.name_}.${key.toString()}` : "ObservableObject.key" options.context = this.proxy_ || this.target_ const cachedDescriptor = getCachedObservablePropDescriptor(key) @@ -478,19 +400,6 @@ export class ObservableObjectAdministration return true } - // Intercept - if (hasInterceptors(this)) { - const change = interceptChange(this, { - object: this.proxy_ || this.target_, - name: key, - type: REMOVE - }) - // Cancelled - if (!change) { - return null - } - } - // Delete try { startBatch() diff --git a/packages/mobx/src/types/observableset.ts b/packages/mobx/src/types/observableset.ts index b23899a73..b43809d0f 100644 --- a/packages/mobx/src/types/observableset.ts +++ b/packages/mobx/src/types/observableset.ts @@ -9,9 +9,6 @@ import { notifyListeners, createInstanceofPredicate, makeIterable, - hasInterceptors, - interceptChange, - IInterceptable, checkIfStateModificationsAreAllowed, untracked, transaction, @@ -43,25 +40,11 @@ export type ISetDidChange = oldValue: T } -export type ISetWillDeleteChange = { - type: "delete" - object: ObservableSet - oldValue: T -} -export type ISetWillAddChange = { - type: "add" - object: ObservableSet - newValue: T -} - -export type ISetWillChange = ISetWillDeleteChange | ISetWillAddChange - -export class ObservableSet implements Set, IInterceptable, IListenable { +export class ObservableSet implements Set, IListenable { [$mobx] = ObservableSetMarker private data_: Set = new Set() atom_!: IAtom changeListeners_ - interceptors_ dehancer: any enhancer_: (newV: any, oldV: any | undefined) => any @@ -109,19 +92,6 @@ export class ObservableSet implements Set, IInterceptable>(this, { - type: ADD, - object: this, - newValue: value - }) - if (!change) { - return this - } - - // implemented reassignment same as it's done for ObservableMap - value = change.newValue! - } if (!this.has(value)) { transaction(() => { this.data_.add(this.enhancer_(value, undefined)) @@ -146,16 +116,6 @@ export class ObservableSet implements Set, IInterceptable>(this, { - type: DELETE, - object: this, - oldValue: value - }) - if (!change) { - return false - } - } if (this.has(value)) { const notify = hasListeners(this) const change = notify diff --git a/packages/mobx/src/types/observablevalue.ts b/packages/mobx/src/types/observablevalue.ts index b043e9bbb..19f1b2b0f 100644 --- a/packages/mobx/src/types/observablevalue.ts +++ b/packages/mobx/src/types/observablevalue.ts @@ -1,16 +1,13 @@ import { Atom, IEnhancer, - IInterceptable, IEqualsComparer, IListenable, checkIfStateModificationsAreAllowed, compareDefault, createInstanceofPredicate, getNextId, - hasInterceptors, hasListeners, - interceptChange, notifyListeners, toPrimitive, globalState, @@ -18,12 +15,6 @@ import { UPDATE } from "../internal" -export interface IValueWillChange { - object: IObservableValue - type: "update" - newValue: T -} - export type IValueDidChange = { type: "update" observableKind: "value" @@ -37,12 +28,8 @@ export interface IObservableValue { set(value: T): void } -export class ObservableValue - extends Atom - implements IObservableValue, IInterceptable>, IListenable -{ +export class ObservableValue extends Atom implements IObservableValue, IListenable { hasUnreportedChange_ = false - interceptors_ changeListeners_ value_ dehancer: any @@ -73,17 +60,6 @@ export class ObservableValue private prepareNewValue_(newValue): T | IUNCHANGED { checkIfStateModificationsAreAllowed(this) - if (hasInterceptors(this)) { - const change = interceptChange>(this, { - object: this, - type: UPDATE, - newValue - }) - if (!change) { - return globalState.UNCHANGED - } - newValue = change.newValue - } // apply modifier newValue = this.enhancer_(newValue, this.value_, this.name_) return this.equals_(this.value_, newValue) ? globalState.UNCHANGED : newValue From e0bdf5cde86190faa6e571817ce90148e3ba2265 Mon Sep 17 00:00:00 2001 From: Michel Weststrate Date: Thu, 30 Jul 2026 22:35:52 +0200 Subject: [PATCH 05/10] refactor(mobx): remove _interceptReads and the dehancer concept --- packages/mobx/__tests__/base/api.js | 1 - packages/mobx/__tests__/base/array.js | 202 +-------------------- packages/mobx/src/api/intercept-read.ts | 58 ------ packages/mobx/src/internal.ts | 1 - packages/mobx/src/mobx.ts | 1 - packages/mobx/src/types/observablearray.ts | 35 +--- packages/mobx/src/types/observablemap.ts | 12 +- packages/mobx/src/types/observableset.ts | 15 +- packages/mobx/src/types/observablevalue.ts | 13 -- 9 files changed, 15 insertions(+), 323 deletions(-) delete mode 100644 packages/mobx/src/api/intercept-read.ts diff --git a/packages/mobx/__tests__/base/api.js b/packages/mobx/__tests__/base/api.js index a04db080f..f447d9f5d 100644 --- a/packages/mobx/__tests__/base/api.js +++ b/packages/mobx/__tests__/base/api.js @@ -41,7 +41,6 @@ test("correct api should be exposed", function () { "has", "_getGlobalState", "getObserverTree", - "_interceptReads", "isAction", "isBoxedObservable", "isComputed", diff --git a/packages/mobx/__tests__/base/array.js b/packages/mobx/__tests__/base/array.js index 2fe2b85f4..c7604faa1 100644 --- a/packages/mobx/__tests__/base/array.js +++ b/packages/mobx/__tests__/base/array.js @@ -1,7 +1,7 @@ "use strict" const mobx = require("../../src/mobx.ts") -const { observable, when, _getAdministration, reaction, computed, makeObservable, autorun } = mobx +const { observable, when, reaction, computed, makeObservable, autorun } = mobx const iterall = require("iterall") let consoleWarnSpy @@ -535,22 +535,6 @@ test("concats correctly #1667", () => { expect(x.data.length).toBe(11000) }) -test("dehances last value on shift/pop", () => { - const x1 = observable([3, 5]) - _getAdministration(x1).dehancer = value => { - return value * 2 - } - expect(x1.shift()).toBe(6) - expect(x1.shift()).toBe(10) - - const x2 = observable([3, 5]) - _getAdministration(x2).dehancer = value => { - return value * 2 - } - expect(x2.pop()).toBe(10) - expect(x2.pop()).toBe(6) -}) - test("#2044 symbol key on array", () => { const x = observable([1, 2]) const s = Symbol("test") @@ -695,190 +679,6 @@ test("very long arrays can be safely passed to nativeArray.concat #2379", () => expect(observableArray).toEqual(anotherArray) }) -describe("dehances", () => { - function supressConsoleWarn(fn) { - const { warn } = console - console.warn = () => {} - const result = fn() - console.warn = warn - return result - } - - const dehancer = thing => { - // Dehance only objects of a proper type - if (thing && typeof thing === "object" && thing.hasOwnProperty("value")) { - return thing.value - } - // Support nested arrays - if (Array.isArray(thing)) { - // If array has own dehancer it's still applied prior to ours. - // It doesn't matter how many dehancers we apply, - // if they ignore unknown types. - return thing.map(dehancer) - } - // Ignore unknown types - return thing - } - - let enhanced, dehanced, array - - beforeEach(() => { - enhanced = [{ value: 1 }, { value: 2 }, { value: 3 }] - dehanced = enhanced.map(dehancer) - array = observable(enhanced) - mobx._getAdministration(array).dehancer = dehancer - }) - - test("slice", () => { - expect(array.slice()).toEqual(dehanced.slice()) - }) - - test("filter", () => { - const predicate = value => value === 2 - expect(array.filter(predicate)).toEqual(dehanced.filter(predicate)) - }) - - test("concat", () => { - expect(array.concat(4)).toEqual(dehanced.concat(4)) - }) - - test("entries", () => { - expect([...array.entries()]).toEqual([...dehanced.entries()]) - }) - - test("every", () => { - array.every((value, index) => { - expect(value).toEqual(dehanced[index]) - return true - }) - }) - - test("find", () => { - const predicate = value => value === 2 - expect(array.find(predicate)).toEqual(dehanced.find(predicate)) - }) - - test("forEach", () => { - array.forEach((value, index) => { - expect(value).toEqual(dehanced[index]) - }) - }) - - test("includes", () => { - expect(array.includes(2)).toEqual(dehanced.includes(2)) - }) - - test("indexOf", () => { - expect(array.indexOf(2)).toEqual(dehanced.indexOf(2)) - }) - - test("join", () => { - expect(array.join()).toEqual(dehanced.join()) - }) - - test("lastIndexOf", () => { - expect(array.lastIndexOf(2)).toEqual(dehanced.lastIndexOf(2)) - }) - - test("map", () => { - array.map((value, index) => { - expect(value).toEqual(dehanced[index]) - return value - }) - }) - - test("pop", () => { - expect(array.pop()).toEqual(dehanced.pop()) - }) - - test("reduce", () => { - array.reduce((_, value, index) => { - expect(value).toEqual(dehanced[index]) - }) - }) - - test("reduceRight", () => { - array.reduceRight((_, value, index) => { - expect(value).toEqual(dehanced[index]) - }) - }) - - test("reverse", () => { - const reversedArray = supressConsoleWarn(() => array.reverse()) - expect(reversedArray).toEqual(dehanced.reverse()) - }) - - test("shift", () => { - expect(array.shift()).toEqual(dehanced.shift()) - }) - - test("some", () => { - array.some((value, index) => { - expect(value).toEqual(dehanced[index]) - return false - }) - }) - - test("splice", () => { - expect(array.splice(1, 2)).toEqual(dehanced.splice(1, 2)) - }) - - test("sort", () => { - const comparator = (a, b) => { - expect(typeof a).toEqual("number") - expect(typeof b).toEqual("number") - return b > a - } - const sortedArray = supressConsoleWarn(() => array.sort(comparator)) - expect(sortedArray).toEqual(dehanced.sort(comparator)) - }) - - test("values", () => { - expect([...array.values()]).toEqual([...dehanced.values()]) - }) - - test("toReversed", () => { - expect(array.toReversed()).toEqual(dehanced.toReversed()) - }) - - test("toSorted", () => { - expect(array.toSorted()).toEqual(dehanced.toSorted()) - }) - - test("toSorted with args", () => { - expect(array.toSorted((a, b) => a - b)).toEqual(dehanced.toSorted((a, b) => a - b)) - }) - - test("toSpliced", () => { - expect(array.toSpliced(1, 2)).toEqual(dehanced.toSpliced(1, 2)) - }) - - test("with", () => { - expect(array.with(1, 5)).toEqual(dehanced.with(1, 5)) - }) - - test("at", () => { - expect(array.at(1)).toEqual(dehanced.at(1)) - expect(array.at(-1)).toEqual(dehanced.at(-1)) - }) - - test("flat/flatMap", () => { - const nestedArray = [{ value: 1 }, [{ value: 2 }, [{ value: 3 }]]] - const dehancedNestedArray = nestedArray.map(dehancer) - - // flat - array.replace(nestedArray) - expect(array.flat(Infinity)).toEqual(dehancedNestedArray.flat(Infinity)) - - // flatMap - const flattenedArray = array.flatMap((value, index) => { - expect(value).toEqual(dehancedNestedArray[index]) - return value - }) - expect(flattenedArray).toEqual(dehancedNestedArray.flat(1)) - }) -}) - test("reduce without initial value #2432", () => { const array = [1, 2, 3] const observableArray = observable(array) diff --git a/packages/mobx/src/api/intercept-read.ts b/packages/mobx/src/api/intercept-read.ts deleted file mode 100644 index 1a741b2b9..000000000 --- a/packages/mobx/src/api/intercept-read.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { - IObservableArray, - IObservableValue, - Lambda, - ObservableMap, - getAdministration, - isObservableArray, - isObservableMap, - isObservableObject, - isObservableValue, - ObservableSet, - die, - isStringish -} from "../internal" - -export type ReadInterceptor = (value: any) => T - -/** Experimental feature right now, tested indirectly via Mobx-State-Tree */ -export function interceptReads(value: IObservableValue, handler: ReadInterceptor): Lambda -export function interceptReads( - observableArray: IObservableArray, - handler: ReadInterceptor -): Lambda -export function interceptReads( - observableMap: ObservableMap, - handler: ReadInterceptor -): Lambda -export function interceptReads( - observableSet: ObservableSet, - handler: ReadInterceptor -): Lambda -export function interceptReads( - object: Object, - property: string, - handler: ReadInterceptor -): Lambda -export function interceptReads(thing, propOrHandler?, handler?): Lambda { - let target - if (isObservableMap(thing) || isObservableArray(thing) || isObservableValue(thing)) { - target = getAdministration(thing) - } else if (isObservableObject(thing)) { - if (__DEV__ && !isStringish(propOrHandler)) { - return die( - `InterceptReads can only be used with a specific property, not with an object in general` - ) - } - target = getAdministration(thing, propOrHandler) - } else if (__DEV__) { - return die(`Expected observable map, object or array as first array`) - } - if (__DEV__ && target.dehancer !== undefined) { - return die(`An intercept reader was already established`) - } - target.dehancer = typeof propOrHandler === "function" ? propOrHandler : handler - return () => { - target.dehancer = undefined - } -} diff --git a/packages/mobx/src/internal.ts b/packages/mobx/src/internal.ts index 37661e5c7..c0f963289 100644 --- a/packages/mobx/src/internal.ts +++ b/packages/mobx/src/internal.ts @@ -34,7 +34,6 @@ export * from "./api/configure" export * from "./api/extendobservable" export * from "./api/extras" export * from "./api/flow" -export * from "./api/intercept-read" export * from "./api/iscomputed" export * from "./api/isobservable" export * from "./api/object-api" diff --git a/packages/mobx/src/mobx.ts b/packages/mobx/src/mobx.ts index 9f3ed30f5..c751bcaee 100644 --- a/packages/mobx/src/mobx.ts +++ b/packages/mobx/src/mobx.ts @@ -126,7 +126,6 @@ export { $mobx, isComputingDerivation as _isComputingDerivation, onReactionError, - interceptReads as _interceptReads, IComputedValueOptions, IActionRunInfo, _startAction, diff --git a/packages/mobx/src/types/observablearray.ts b/packages/mobx/src/types/observablearray.ts index 7d96432f6..f6e171a73 100644 --- a/packages/mobx/src/types/observablearray.ts +++ b/packages/mobx/src/types/observablearray.ts @@ -93,7 +93,6 @@ export class ObservableArrayAdministration implements IListenable { readonly values_: any[] = [] // this is the prop that gets proxied, so can't replace it! changeListeners_ enhancer_: (newV: any, oldV: any | undefined) => any - dehancer: any proxy_!: IObservableArray lastKnownLength_ = 0 @@ -107,20 +106,6 @@ export class ObservableArrayAdministration implements IListenable { enhancer(newV, oldV, __DEV__ ? name + "[..]" : "ObservableArray[..]") } - dehanceValue_(value: any): any { - if (this.dehancer !== undefined) { - return this.dehancer(value) - } - return value - } - - dehanceValues_(values: any[]): any[] { - if (this.dehancer !== undefined && values.length > 0) { - return values.map(this.dehancer) as any - } - return values - } - getArrayLength_(): number { this.atom_.reportObserved() return this.values_.length @@ -183,7 +168,7 @@ export class ObservableArrayAdministration implements IListenable { if (deleteCount !== 0 || newItems.length !== 0) { this.notifyArraySplice_(index, newItems, res) } - return this.dehanceValues_(res) + return res } spliceItemsIntoValues_(index: number, deleteCount: number, newItems: any[]): any[] { @@ -251,7 +236,7 @@ export class ObservableArrayAdministration implements IListenable { get_(index: number): any | undefined { this.atom_.reportObserved() - return this.dehanceValue_(this.values_[index]) + return this.values_[index] } set_(index: number, newValue: any) { @@ -382,7 +367,7 @@ export var arrayExtensions = { remove(value: any): boolean { const adm: ObservableArrayAdministration = this[$mobx] - const idx = adm.dehanceValues_(adm.values_).indexOf(value) + const idx = adm.values_.indexOf(value) if (idx > -1) { this.splice(idx, 1) return true @@ -431,13 +416,13 @@ function addArrayExtension(funcName, funcFactory) { } } -// Report and delegate to dehanced array +// Report and delegate to the backing array function simpleFunc(funcName) { return function () { const adm: ObservableArrayAdministration = this[$mobx] adm.atom_.reportObserved() - const dehancedValues = adm.dehanceValues_(adm.values_) - return dehancedValues[funcName].apply(dehancedValues, arguments) + const values = adm.values_ + return values[funcName].apply(values, arguments) } } @@ -446,8 +431,8 @@ function mapLikeFunc(funcName) { return function (callback, thisArg) { const adm: ObservableArrayAdministration = this[$mobx] adm.atom_.reportObserved() - const dehancedValues = adm.dehanceValues_(adm.values_) - return dehancedValues[funcName]((element, index) => { + const values = adm.values_ + return values[funcName]((element, index) => { return callback.call(thisArg, element, index, this) }) } @@ -458,13 +443,13 @@ function reduceLikeFunc(funcName) { return function () { const adm: ObservableArrayAdministration = this[$mobx] adm.atom_.reportObserved() - const dehancedValues = adm.dehanceValues_(adm.values_) + const values = adm.values_ // #2432 - reduce behavior depends on arguments.length const callback = arguments[0] arguments[0] = (accumulator, currentValue, index) => { return callback(accumulator, currentValue, index, this) } - return dehancedValues[funcName].apply(dehancedValues, arguments) + return values[funcName].apply(values, arguments) } } diff --git a/packages/mobx/src/types/observablemap.ts b/packages/mobx/src/types/observablemap.ts index db9814b21..0f1c3050c 100644 --- a/packages/mobx/src/types/observablemap.ts +++ b/packages/mobx/src/types/observablemap.ts @@ -76,7 +76,6 @@ export class ObservableMap implements Map, IListenable { hasMap_!: Map> // hasMap, not hashMap >-). keysAtom_!: IAtom changeListeners_ - dehancer: any constructor( initialData?: IObservableMapInitialValues, @@ -210,9 +209,9 @@ export class ObservableMap implements Map, IListenable { get(key: K): V | undefined { if (this.has(key)) { - return this.dehanceValue_(this.data_.get(key)!.get()) + return this.data_.get(key)!.get() } - return this.dehanceValue_(undefined) + return undefined } getOrInsert(key: K, value: V): V { @@ -229,13 +228,6 @@ export class ObservableMap implements Map, IListenable { return this.get(key)! } - private dehanceValue_(value: X): X { - if (this.dehancer !== undefined) { - return this.dehancer(value) - } - return value - } - keys(): MapIterator { this.keysAtom_.reportObserved() return this.data_.keys() diff --git a/packages/mobx/src/types/observableset.ts b/packages/mobx/src/types/observableset.ts index b43809d0f..d8764a29c 100644 --- a/packages/mobx/src/types/observableset.ts +++ b/packages/mobx/src/types/observableset.ts @@ -45,7 +45,6 @@ export class ObservableSet implements Set, IListenable { private data_: Set = new Set() atom_!: IAtom changeListeners_ - dehancer: any enhancer_: (newV: any, oldV: any | undefined) => any constructor( @@ -62,13 +61,6 @@ export class ObservableSet implements Set, IListenable { }) } - private dehanceValue_(value: X): X { - if (this.dehancer !== undefined) { - return this.dehancer(value) - } - return value - } - clear() { transaction(() => { untracked(() => { @@ -142,7 +134,7 @@ export class ObservableSet implements Set, IListenable { has(value: T) { this.atom_.reportObserved() - return this.data_.has(this.dehanceValue_(value)) + return this.data_.has(value) } entries() { @@ -161,14 +153,11 @@ export class ObservableSet implements Set, IListenable { values(): SetIterator { this.atom_.reportObserved() - const self = this const values = this.data_.values() return makeIterableForSet({ next() { const { value, done } = values.next() - return !done - ? { value: self.dehanceValue_(value), done } - : { value: undefined, done } + return !done ? { value, done } : { value: undefined, done } } }) } diff --git a/packages/mobx/src/types/observablevalue.ts b/packages/mobx/src/types/observablevalue.ts index 19f1b2b0f..7224104c6 100644 --- a/packages/mobx/src/types/observablevalue.ts +++ b/packages/mobx/src/types/observablevalue.ts @@ -32,7 +32,6 @@ export class ObservableValue extends Atom implements IObservableValue, ILi hasUnreportedChange_ = false changeListeners_ value_ - dehancer: any constructor( value: T, @@ -44,13 +43,6 @@ export class ObservableValue extends Atom implements IObservableValue, ILi this.value_ = enhancer_(value, undefined, name_) } - private dehanceValue(value: T): T { - if (this.dehancer !== undefined) { - return this.dehancer(value) - } - return value - } - public set(newValue: T) { newValue = this.prepareNewValue_(newValue) as any if (newValue !== globalState.UNCHANGED) { @@ -81,11 +73,6 @@ export class ObservableValue extends Atom implements IObservableValue, ILi public get(): T { this.reportObserved() - return this.dehanceValue(this.value_) - } - - raw() { - // used by MST ot get undehanced value return this.value_ } From 73ba575fb64a13ec8564c0971f6de331169f4559 Mon Sep 17 00:00:00 2001 From: Michel Weststrate Date: Thu, 30 Jul 2026 22:51:38 +0200 Subject: [PATCH 06/10] refactor(mobx): remove observe API and change-listener machinery --- docs/api.md | 7 - docs/intercept-and-observe.md | 154 ---------------- packages/mobx/__tests__/base/action.js | 10 +- packages/mobx/__tests__/base/api.js | 1 - packages/mobx/__tests__/base/array.js | 70 +------ packages/mobx/__tests__/base/babel-tests.js | 48 ----- packages/mobx/__tests__/base/errorhandling.js | 4 +- packages/mobx/__tests__/base/makereactive.js | 30 ++- packages/mobx/__tests__/base/map.js | 18 -- packages/mobx/__tests__/base/observables.js | 171 ++++-------------- packages/mobx/__tests__/base/observe.ts | 55 ------ packages/mobx/__tests__/base/set.js | 30 --- .../base/stage3-decorators-inheritance.ts | 15 -- .../mobx/__tests__/base/stage3-decorators.ts | 114 ------------ packages/mobx/__tests__/base/tojs.js | 20 +- .../mobx/__tests__/base/typescript-tests.ts | 104 ----------- packages/mobx/__tests__/perf/perf.js | 30 ++- packages/mobx/src/api/observe.ts | 148 --------------- packages/mobx/src/internal.ts | 2 - packages/mobx/src/mobx.ts | 9 - packages/mobx/src/types/listen-utils.ts | 33 ---- packages/mobx/src/types/observablearray.ts | 78 +------- packages/mobx/src/types/observablemap.ts | 77 +------- packages/mobx/src/types/observableobject.ts | 93 +--------- packages/mobx/src/types/observableset.ts | 51 +----- packages/mobx/src/types/observablevalue.ts | 26 +-- website/i18n/en.json | 4 - website/sidebars.json | 3 +- 28 files changed, 83 insertions(+), 1322 deletions(-) delete mode 100644 docs/intercept-and-observe.md delete mode 100644 packages/mobx/__tests__/base/observe.ts delete mode 100644 packages/mobx/src/api/observe.ts delete mode 100644 packages/mobx/src/types/listen-utils.ts diff --git a/docs/api.md b/docs/api.md index 9d9774f88..be2524bd6 100644 --- a/docs/api.md +++ b/docs/api.md @@ -278,13 +278,6 @@ _Utilities that might make working with observable objects or computed values mo Attaches a global error listener, which is invoked for every error that is thrown from a _reaction_. This can be used for monitoring or test purposes. -### `observe` - -{🚀} Usage: `observe(propertyName|array|object|Set|Map, listener)` -([further information](intercept-and-observe.md#observe)) - -Low-level API that can be used to observe a single observable value. Returns a disposer function that stops the interception. - ### `onBecomeObserved` {🚀} Usage: `onBecomeObserved(observable, property?, listener: () => void)` diff --git a/docs/intercept-and-observe.md b/docs/intercept-and-observe.md deleted file mode 100644 index e65f15d8f..000000000 --- a/docs/intercept-and-observe.md +++ /dev/null @@ -1,154 +0,0 @@ ---- -title: Intercept & Observe -sidebar_label: Intercept & Observe {🚀} -hide_title: true ---- - - - -# Intercept & Observe {🚀} - -_⚠️ **Warning**: intercept and observe are low level utilities, and should not be needed in practice. Use some form of [reaction](reactions.md) instead, as `observe` doesn't respect transactions and doesn't support deep observing of changes. Using these utilities is an anti-pattern. If you intend to get access to the old and new value using `observe`, use [`reaction`](reactions.md#reaction) instead. ⚠️_ - -`observe` and `intercept` can be used to monitor the changes of a single observable, but they **_don't_** track nested observables. - -- `intercept` can be used to detect and modify mutations before they are applied to the observable (validating, normalizing or cancelling). -- `observe` allows you to intercept changes after they have been made. - -## Intercept - -Usage: `intercept(target, propertyName?, interceptor)` - -_Please avoid this API. It basically provides a bit of aspect-oriented programming, creating flows that are really hard to debug. Instead, do things like data validation **before** updating any state, rather than during._ - -- `target`: the observable to guard. -- `propertyName`: optional parameter to specify a specific property to intercept. Note that `intercept(user.name, interceptor)` is fundamentally different from `intercept(user, "name", interceptor)`. The first tries to add an interceptor to the _current_ `value` inside `user.name`, which might not be an observable at all. The latter intercepts changes to the `name` _property_ of `user`. -- `interceptor`: callback that is invoked for _each_ change that is made to the observable. Receives a single change object describing the mutation. - -The `intercept` should tell MobX what needs to happen with the current change. -Therefore it should do one of the following things: - -1. Return the received `change` object as-is from the function, in which case the mutation will be applied. -2. Modify the `change` object and return it, for example to normalize the data. Not all fields are modifiable, see below. -3. Return `null`, this indicates that the change can be ignored and shouldn't be applied. This is a powerful concept with which you can for example make your objects temporarily immutable. -4. Throw an exception, if for example some invariant isn't met. - -The function returns a `disposer` function that can be used to cancel the interceptor when invoked. -It is possible to register multiple interceptors to the same observable. -They will be chained in registration order. -If one of the interceptors returns `null` or throws an exception, the other interceptors won't be evaluated anymore. -It is also possible to register an interceptor both on a parent object and on an individual property. -In that case the parent object interceptors are run before the property interceptors. - -```javascript -const theme = observable({ - backgroundColor: "#ffffff" -}) - -const disposer = intercept(theme, "backgroundColor", change => { - if (!change.newValue) { - // Ignore attempts to unset the background color. - return null - } - if (change.newValue.length === 6) { - // Correct missing '#' prefix. - change.newValue = "#" + change.newValue - return change - } - if (change.newValue.length === 7) { - // This must be a properly formatted color code! - return change - } - if (change.newValue.length > 10) { - // Stop intercepting future changes. - disposer() - } - throw new Error("This doesn't look like a color at all: " + change.newValue) -}) -``` - -## Observe - -Usage: `observe(target, propertyName?, listener, invokeImmediately?)` - -_See above notice, please avoid this API and use [`reaction`](reactions.md#reaction) instead._ - -- `target`: the observable to observe. -- `propertyName`: optional parameter to specify a specific property to observe. Note that `observe(user.name, listener)` is fundamentally different from `observe(user, "name", listener)`. The first observes the _current_ `value` inside `user.name`, which might not be an observable at all. The latter observes the `name` _property_ of `user`. -- `listener`: callback that will be invoked for _each_ change that is made to the observable. Receives a single change object describing the mutation, except for boxed observables, which will invoke the `listener` with two parameters: `newValue, oldValue`. -- `invokeImmediately`: _false_ by default. Set it to _true_ if you want `observe` to invoke the `listener` directly with the state of the observable, instead of waiting for the first change. Not supported (yet) by all kinds of observables. - -The function returns a `disposer` function that can be used to cancel the observer. -Note that `transaction` does not affect the working of the `observe` method(s). -This means that even inside a transaction `observe` will fire its listeners for each mutation. -Hence [`autorun`](reactions.md#autorun) is usually a more powerful and declarative alternative to `observe`. - -_`observe` reacts to **mutations** when they are being made, while reactions like `autorun` or `reaction` react to **new values** when they become available. In many cases the latter is sufficient._ - -Example: - -```javascript -import { observable, observe } from "mobx" - -const person = observable({ - firstName: "Maarten", - lastName: "Luther" -}) - -// Observe all fields. -const disposer = observe(person, change => { - console.log(change.type, change.name, "from", change.oldValue, "to", change.object[change.name]) -}) - -person.firstName = "Martin" -// Prints: 'update firstName from Maarten to Martin' - -// Ignore any future updates. -disposer() - -// Observe a single field. -const disposer2 = observe(person, "lastName", change => { - console.log("LastName changed to ", change.newValue) -}) -``` - -Related blog: [Object.observe is dead. Long live mobx.observe](https://medium.com/@mweststrate/object-observe-is-dead-long-live-mobservable-observe-ad96930140c5) - -## Event overview - -The callbacks of `intercept` and `observe` will receive an event object which has at least the following properties: - -- `object`: the observable triggering the event. -- `debugObjectName`: the name of the observable triggering the event (for debugging). -- `observableKind`: the type of the observable (value, set, array, object, map, computed). -- `type` (string): the type of the current event. - -These are the additional fields that are available per type: - -| Observable type | Event type | Property | Description | Available during intercept | Can be modified by intercept | -| ---------------------------- | ---------- | ------------ | ------------------------------------------------------------------------------------------------- | -------------------------- | ---------------------------- | -| Object | add | name | Name of the property being added. | √ | | -| | | newValue | The new value being assigned. | √ | √ | -| | update\* | name | Name of the property being updated. | √ | | -| | | newValue | The new value being assigned. | √ | √ | -| | | oldValue | The value that is replaced. | | | -| Array | splice | index | Starting index of the splice. Splices are also fired by `push`, `unshift`, `replace`, etc. | √ | | -| | | removedCount | Amount of items being removed. | √ | √ | -| | | added | Array with items being added. | √ | √ | -| | | removed | Array with items that were removed. | | | -| | | addedCount | Amount of items that were added. | | | -| | update | index | Index of the single entry being updated. | √ | | -| | | newValue | The newValue that is / will be assigned. | √ | √ | -| | | oldValue | The old value that was replaced. | | | -| Map | add | name | The name of the entry that was added. | √ | | -| | | newValue | The new value that is being assigned. | √ | √ | -| | update | name | The name of the entry being updated. | √ | | -| | | newValue | The new value that is being assigned. | √ | √ | -| | | oldValue | The value that has been replaced. | | | -| | delete | name | The name of the entry being removed. | √ | | -| | | oldValue | The value of the entry that was removed. | | | -| Boxed & computed observables | create | newValue | The value that was assigned during creation. Only available as `spy` event for boxed observables. | | | -| | update | newValue | The new value being assigned. | √ | √ | -| | | oldValue | The previous value of the observable. | | | - -**Note:** object `update` events won't fire for updated computed values (as those aren't mutations). But it is possible to observe them by explicitly subscribing to the specific property using `observe(object, 'computedPropertyName', listener)`. diff --git a/packages/mobx/__tests__/base/action.js b/packages/mobx/__tests__/base/action.js index 73983173b..74852e141 100644 --- a/packages/mobx/__tests__/base/action.js +++ b/packages/mobx/__tests__/base/action.js @@ -69,13 +69,9 @@ test("action modifications should be picked up 3", () => { const doubler = mobx.computed(() => a.get() * 2) - mobx.observe( - doubler, - () => { - b = doubler.get() - }, - true - ) + mobx.autorun(() => { + b = doubler.get() + }) expect(b).toBe(2) diff --git a/packages/mobx/__tests__/base/api.js b/packages/mobx/__tests__/base/api.js index f447d9f5d..ba619972f 100644 --- a/packages/mobx/__tests__/base/api.js +++ b/packages/mobx/__tests__/base/api.js @@ -62,7 +62,6 @@ test("correct api should be exposed", function () { "observableRef", "observableShallow", "observableStruct", - "observe", "onReactionError", "onBecomeObserved", "onBecomeUnobserved", diff --git a/packages/mobx/__tests__/base/array.js b/packages/mobx/__tests__/base/array.js index c7604faa1..14362724a 100644 --- a/packages/mobx/__tests__/base/array.js +++ b/packages/mobx/__tests__/base/array.js @@ -212,70 +212,6 @@ test("concat should automatically slice observable arrays, #260", () => { expect(a1.concat(a2)).toEqual([1, 2, 3, 4]) }) -test("observe", function () { - const ar = mobx.observable([1, 4]) - const buf = [] - const disposer = mobx.observe( - ar, - function (changes) { - buf.push(changes) - }, - true - ) - - ar[1] = 3 // 1,3 - ar[2] = 0 // 1, 3, 0 - ar.shift() // 3, 0 - ar.push(1, 2) // 3, 0, 1, 2 - ar.splice(1, 2, 3, 4) // 3, 3, 4, 2 - expect(ar.slice()).toEqual([3, 3, 4, 2]) - ar.splice(6) - ar.splice(6, 2) - ar.replace(["a"]) - ar.pop() - ar.pop() // does not fire anything - - // check the object param - buf.forEach(function (change) { - expect(change.object).toBe(ar) - delete change.object - expect(change.observableKind).toBe("array") - delete change.observableKind - delete change.debugObjectName - }) - - const result = [ - { type: "splice", index: 0, addedCount: 2, removed: [], added: [1, 4], removedCount: 0 }, - { type: "update", index: 1, oldValue: 4, newValue: 3 }, - { type: "splice", index: 2, addedCount: 1, removed: [], added: [0], removedCount: 0 }, - { type: "splice", index: 0, addedCount: 0, removed: [1], added: [], removedCount: 1 }, - { type: "splice", index: 2, addedCount: 2, removed: [], added: [1, 2], removedCount: 0 }, - { - type: "splice", - index: 1, - addedCount: 2, - removed: [0, 1], - added: [3, 4], - removedCount: 2 - }, - { - type: "splice", - index: 0, - addedCount: 1, - removed: [3, 3, 4, 2], - added: ["a"], - removedCount: 4 - }, - { type: "splice", index: 0, addedCount: 0, removed: ["a"], added: [], removedCount: 1 } - ] - - expect(buf).toEqual(result) - - disposer() - ar[0] = 5 - expect(buf).toEqual(result) -}) - test("array modification1", function () { const a = mobx.observable([1, 2, 3]) const r = a.splice(-10, 5, 4, 5, 6) @@ -414,12 +350,16 @@ test("react to sort changes", function () { test("autoextend buffer length", function () { const ar = observable(new Array(1000)) let changesCount = 0 - mobx.observe(ar, () => ++changesCount) + const d = mobx.reaction( + () => ar.length, + () => ++changesCount + ) ar[ar.length] = 0 ar.push(0) expect(changesCount).toBe(2) + d() }) test("array exposes correct keys", () => { diff --git a/packages/mobx/__tests__/base/babel-tests.js b/packages/mobx/__tests__/base/babel-tests.js index b2765e80e..a22dff1c0 100644 --- a/packages/mobx/__tests__/base/babel-tests.js +++ b/packages/mobx/__tests__/base/babel-tests.js @@ -11,7 +11,6 @@ import { action, actionBound, isObservableObject, - observe, isObservable, isObservableProp, isComputedProp, @@ -187,28 +186,6 @@ test("decorators", function () { expect(isObservableProp(o, "amount")).toBe(true) expect(o.total).toBe(6) // .... this is required to initialize the props which are made reactive lazily... expect(isObservableProp(o, "total")).toBe(true) - - const events = [] - const d1 = observe(o, ev => events.push(ev.name, ev.oldValue)) - const d2 = observe(o, "price", ev => events.push(ev.newValue, ev.oldValue)) - const d3 = observe(o, "total", ev => events.push(ev.newValue, ev.oldValue)) - - o.price = 4 - - d1() - d2() - d3() - - o.price = 5 - - expect(events).toEqual([ - 8, // new total - 6, // old total - 4, // new price - 3, // old price - "price", // event name - 3 // event oldValue - ]) }) test("issue 191 - shared initializers (babel)", function () { @@ -399,31 +376,6 @@ test("267 (babel) should be possible to declare properties observable outside st Store // just to avoid linter warning }) -test("288 atom not detected for object property", () => { - class Store { - foo = "" - - constructor() { - makeObservable(this, { - foo: mobx.observable - }) - } - } - - const store = new Store() - let changed = false - - mobx.observe( - store, - "foo", - () => { - changed = true - }, - true - ) - expect(changed).toBe(true) -}) - test.skip("observable performance - babel", () => { const AMOUNT = 100000 diff --git a/packages/mobx/__tests__/base/errorhandling.js b/packages/mobx/__tests__/base/errorhandling.js index 68fd14e2b..e2cec9a73 100644 --- a/packages/mobx/__tests__/base/errorhandling.js +++ b/packages/mobx/__tests__/base/errorhandling.js @@ -245,7 +245,7 @@ test("cycle1", function () { return p.get() * 2 }) // thats a cycle! utils.consoleError(() => { - mobx.observe(p, voidObserver, true) + mobx.autorun(() => p.get()) }, /Cycle detected/) checkGlobalState() }) @@ -282,7 +282,7 @@ test("cycle4", function () { return a.get() * 2 }) - m.observe(b, voidObserver) + m.autorun(() => b.get()) expect(1).toBe(a.get()) utils.consoleError(() => { diff --git a/packages/mobx/__tests__/base/makereactive.js b/packages/mobx/__tests__/base/makereactive.js index e81f9052e..6097193dd 100644 --- a/packages/mobx/__tests__/base/makereactive.js +++ b/packages/mobx/__tests__/base/makereactive.js @@ -163,15 +163,12 @@ test("observable4", function () { const x = m.observable([{ x: 1 }, { x: 2 }]) const b = buffer() - m.observe( - m.computed(function () { - return x.map(function (d) { - return d.x - }) - }), - x => b(x.newValue), - true - ) + const c = m.computed(function () { + return x.map(function (d) { + return d.x + }) + }) + m.autorun(() => b(c.get())) x[0].x = 3 x.shift() @@ -182,15 +179,12 @@ test("observable4", function () { const x2 = o.array([{ x: 1 }, { x: 2 }], { deep: false }) const b2 = buffer() - m.observe( - m.computed(function () { - return x2.map(function (d) { - return d.x - }) - }), - x => b2(x.newValue), - true - ) + const c2 = m.computed(function () { + return x2.map(function (d) { + return d.x + }) + }) + m.autorun(() => b2(c2.get())) x2[0].x = 3 x2.shift() diff --git a/packages/mobx/__tests__/base/map.js b/packages/mobx/__tests__/base/map.js index c9e3c2f2e..d1c7d6515 100644 --- a/packages/mobx/__tests__/base/map.js +++ b/packages/mobx/__tests__/base/map.js @@ -20,14 +20,7 @@ import { grabConsole } from "../utils/test-utils" test("map crud", function () { mobx._getGlobalState().mobxGuid = 0 // hmm dangerous reset? - const events = [] const m = map({ 1: "a" }) - mobx.observe(m, function (change) { - events.push(change) - expect(change.observableKind).toBe("map") - delete change.observableKind - delete change.debugObjectName - }) expect(m.has("1")).toBe(true) expect(m.has(1)).toBe(false) @@ -86,17 +79,6 @@ test("map crud", function () { expect(m.get("a")).toBe(undefined) expect(m.get("b")).toBe(undefined) - expect(events).toEqual([ - { object: m, name: "1", newValue: "aa", oldValue: "a", type: "update" }, - { object: m, name: 1, newValue: "b", type: "add" }, - { object: m, name: ["arr"], newValue: "arrVal", type: "add" }, - { object: m, name: s, newValue: "symbol-value", type: "add" }, - { object: m, name: "1", oldValue: "aa", type: "delete" }, - { object: m, name: 1, oldValue: "b", type: "delete" }, - { object: m, name: ["arr"], oldValue: "arrVal", type: "delete" }, - { object: m, name: s, oldValue: "symbol-value", type: "delete" } - ]) - expect(JSON.stringify(m)).toBe("[]") }) diff --git a/packages/mobx/__tests__/base/observables.js b/packages/mobx/__tests__/base/observables.js index cc067111e..fc0329a43 100644 --- a/packages/mobx/__tests__/base/observables.js +++ b/packages/mobx/__tests__/base/observables.js @@ -22,13 +22,13 @@ const voidObserver = function () {} function buffer() { const b = [] - const res = function (x) { - if (typeof x.newValue === "object") { - const copy = { ...x.newValue } + const res = function (v) { + if (v !== null && typeof v === "object") { + const copy = { ...v } delete copy[$mobx] b.push(copy) } else { - b.push(x.newValue) + b.push(v) } } res.toArray = function () { @@ -47,7 +47,7 @@ test("argumentless observable", () => { test("basic", function () { const x = observable.box(3) const b = buffer() - m.observe(x, b) + m.reaction(() => x.get(), b) expect(3).toBe(x.get()) x.set(5) @@ -65,7 +65,7 @@ test("basic2", function () { return x.get() * 3 }) - m.observe(z, voidObserver) + m.autorun(() => z.get()) expect(z.get()).toBe(6) expect(y.get()).toBe(9) @@ -89,7 +89,7 @@ test("computed with asStructure modifier", function () { { equals: compareStructural } ) const b = buffer() - m.observe(y, b, true) + m.reaction(() => y.get(), b, { fireImmediately: true }) expect(8).toBe(y.get().sum) @@ -113,7 +113,7 @@ test("dynamic", function (done) { return x.get() }) const b = buffer() - m.observe(y, b, true) + m.reaction(() => y.get(), b, { fireImmediately: true }) expect(3).toBe(y.get()) // First evaluation here.. @@ -138,7 +138,7 @@ test("dynamic2", function (done) { expect(9).toBe(y.get()) const b = buffer() - m.observe(y, b) + m.reaction(() => y.get(), b) x.set(5) expect(25).toBe(y.get()) @@ -162,7 +162,7 @@ test("box uses equals", function (done) { }) const b = buffer() - m.observe(x, b) + m.reaction(() => x.get(), b) x.set("A") x.set("b") @@ -191,7 +191,7 @@ test("box uses equals2", function (done) { }) const b = buffer() - m.observe(y, b) + m.reaction(() => y.get(), b) x.set("2") x.set("02") @@ -220,7 +220,7 @@ test("readme1", function (done) { return order.price.get() * (1 + vat.get()) }) - m.observe(order.priceWithVat, b) + m.reaction(() => order.priceWithVat.get(), b) order.price.set(20) expect([24]).toEqual(b.toArray()) @@ -245,7 +245,7 @@ test("batch", function () { return c.get() * b.get() }) const buf = buffer() - m.observe(d, buf) + m.reaction(() => d.get(), buf) a.set(4) b.set(5) @@ -334,7 +334,7 @@ test("scope", function () { } const order = new Order() - m.observe(order.total, voidObserver) + m.autorun(() => order.total.get()) order.price.set(10) order.amount.set(3) expect(36).toBe(order.total.get()) @@ -467,117 +467,6 @@ test("observe property", function () { expect(mb).toEqual([undefined, 15]) }) -test("observe object", function () { - let events = [] - const a = observable({ - a: 1, - get da() { - return this.a * 2 - } - }) - const stop = m.observe(a, function (change) { - expect(change.observableKind).toEqual("object") - delete change.observableKind - delete change.debugObjectName - events.push(change) - }) - - a.a = 2 - mobx.extendObservable(a, { - b: 3 - }) - a.a = 4 - a.b = 5 - expect(events).toEqual([ - { - type: "update", - object: a, - name: "a", - newValue: 2, - oldValue: 1 - }, - { - type: "add", - object: a, - newValue: 3, - name: "b" - }, - { - type: "update", - object: a, - name: "a", - newValue: 4, - oldValue: 2 - }, - { - type: "update", - object: a, - name: "b", - newValue: 5, - oldValue: 3 - } - ]) - - stop() - events = [] - a.a = 6 - expect(events.length).toBe(0) -}) - -test("mobx.observe", function () { - const events = [] - const o = observable({ b: 2 }) - const ar = observable([3]) - const map = mobx.observable.map({}) - - const push = function (event) { - delete event.debugObjectName - events.push(event) - } - - const stop2 = mobx.observe(o, push) - const stop3 = mobx.observe(ar, push) - const stop4 = mobx.observe(map, push) - - o.b = 5 - ar[0] = 6 - map.set("d", 7) - - stop2() - stop3() - stop4() - - o.b = 9 - ar[0] = 10 - map.set("d", 11) - - expect(events).toEqual([ - { - type: "update", - observableKind: "object", - object: o, - name: "b", - newValue: 5, - oldValue: 2 - }, - { - object: ar, - type: "update", - observableKind: "array", - index: 0, - newValue: 6, - oldValue: 3 - }, - { - type: "add", - observableKind: "map", - object: map, - newValue: 7, - name: "d" - } - ]) -}) - test("change count optimization", function () { let bCalcs = 0 let cCalcs = 0 @@ -591,7 +480,7 @@ test("change count optimization", function () { return b.get() }) - m.observe(c, voidObserver) + m.autorun(() => c.get()) expect(b.get()).toBe(4) expect(c.get()).toBe(4) @@ -619,7 +508,7 @@ test("observables removed", function () { }) expect(calcs).toBe(0) - m.observe(c, voidObserver) + m.autorun(() => c.get()) expect(c.get()).toBe(4) expect(calcs).toBe(1) a.set(2) @@ -677,12 +566,11 @@ test("lazy evaluation", function () { return b.get() * 2 }) - const handle = m.observe( - d, + const handle = m.reaction( + () => d.get(), function () { observerChanges += 1 - }, - false + } ) expect(bCalcs).toBe(4) expect(cCalcs).toBe(3) @@ -777,12 +665,12 @@ test("nested observable2", function () { }) const b = [] - m.observe( - total, - function (x) { - b.push(x.newValue) + m.reaction( + () => total.get(), + function (v) { + b.push(v) }, - true + { fireImmediately: true } ) price.set(150) @@ -996,7 +884,7 @@ test("computed values believe NaN === NaN", function () { return String(a.get() * b.get()) }) const buf = buffer() - m.observe(c, buf) + m.reaction(() => c.get(), buf) a.set(NaN) b.set(NaN) @@ -1017,9 +905,12 @@ test("computed values believe deep NaN === deep NaN when using compareStructural ) const buf = new buffer() - m.observe(c, newValue => { - buf(newValue) - }) + m.reaction( + () => c.get(), + newValue => { + buf(newValue) + } + ) a.b = { a: NaN } a.b = { a: NaN } diff --git a/packages/mobx/__tests__/base/observe.ts b/packages/mobx/__tests__/base/observe.ts deleted file mode 100644 index 2ea1db736..000000000 --- a/packages/mobx/__tests__/base/observe.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { observable, observe, computed } from "../../src/mobx" - -test("observe object and map properties", () => { - const map = observable.map({ a: 1 }) - const events: any[] = [] - - expect(() => observe(map, "b", () => {})).toThrow( - /the entry 'b' does not exist in the observable map/ - ) - - const d1 = observe(map, "a", e => events.push([e.newValue, e.oldValue])) - - map.set("a", 2) - map.set("a", 3) - d1() - map.set("a", 4) - - const o = observable({ a: 5 }) - - expect(() => observe(o, "b" as any, () => {})).toThrow( - /no observable property 'b' found on the observable object/ - ) - const d2 = observe(o, "a", e => events.push([e.newValue, e.oldValue])) - - o.a = 6 - o.a = 7 - d2() - o.a = 8 - - expect(events).toEqual([ - [2, 1], - [3, 2], - [6, 5], - [7, 6] - ]) -}) - -test("observe computed values", () => { - const events: any[] = [] - - const v = observable.box(0) - const f = observable.box(0) - const c = computed(() => v.get()) - - observe(c, e => { - v.get() - f.get() - events.push([e.newValue, e.oldValue]) - }) - - v.set(6) - f.set(10) - - expect(events).toEqual([[6, 0]]) -}) diff --git a/packages/mobx/__tests__/base/set.js b/packages/mobx/__tests__/base/set.js index 9d109488a..36f638ca8 100644 --- a/packages/mobx/__tests__/base/set.js +++ b/packages/mobx/__tests__/base/set.js @@ -6,16 +6,8 @@ const autorun = mobx.autorun const iterall = require("iterall") test("set crud", function () { - const events = [] const s = set([1]) - mobx.observe(s, change => { - expect(change.observableKind).toEqual("set") - delete change.observableKind - delete change.debugObjectName - events.push(change) - }) - expect(s.has(1)).toBe(true) expect(s.has("1")).toBe(false) expect(s.size).toBe(1) @@ -66,16 +58,6 @@ test("set crud", function () { expect(s.has("2")).toBe(false) expect(s.has(3)).toBe(false) expect(s.has(4)).toBe(false) - - expect(events).toEqual([ - { object: s, newValue: "2", type: "add" }, - { object: s, oldValue: 1, type: "delete" }, - { object: s, oldValue: "2", type: "delete" }, - { object: s, newValue: 3, type: "add" }, - { object: s, oldValue: 3, type: "delete" }, - { object: s, newValue: 4, type: "add" }, - { object: s, oldValue: 4, type: "delete" } - ]) }) test("observe value", function () { @@ -282,18 +264,6 @@ test("getAtom", () => { expect(mobx.isObservable(x)).toBeTruthy() }) -test("observe", () => { - const vals = [] - const x = set([1]) - mobx.observe(x, change => { - delete change.debugObjectName - vals.push(change) - }) - x.add(2) - x.add(1) - expect(vals).toEqual([{ newValue: 2, object: x, type: "add", observableKind: "set" }]) -}) - test("toJS", () => { const x = mobx.observable({ x: 1 }) const y = set([x, 1]) diff --git a/packages/mobx/__tests__/base/stage3-decorators-inheritance.ts b/packages/mobx/__tests__/base/stage3-decorators-inheritance.ts index 1e426d1d2..81bfeaeb2 100644 --- a/packages/mobx/__tests__/base/stage3-decorators-inheritance.ts +++ b/packages/mobx/__tests__/base/stage3-decorators-inheritance.ts @@ -5,7 +5,6 @@ import { isComputedProp, isFlow, isObservableProp, - observe, runInAction } from "../../src/mobx" import { action, actionBound, computed, flow, observable } from "../../src/mobx" @@ -57,16 +56,11 @@ test("computed override can delegate to parent computed with super", () => { expect(isObservableProp(child, "missing")).toBe(false) expect(isComputedProp(child, "missing")).toBe(false) - // Observing by public property key must observe the child computed, not the parent computed - const seen: number[] = [] - const dispose = observe(child, "number", change => seen.push(change.newValue), true) - // Direct reads still delegate through super and remain reactive after materialization expect(child.number).toBe(2) runInAction(() => { child.count = 2 }) - dispose() expect(isObservableProp(child, "number")).toBe(true) expect(isComputedProp(child, "number")).toBe(true) @@ -74,7 +68,6 @@ test("computed override can delegate to parent computed with super", () => { expect(isComputedProp(child, "count")).toBe(false) expect(isObservableProp(child, "missing")).toBe(false) expect(isComputedProp(child, "missing")).toBe(false) - expect(seen).toEqual([2, 3]) }) test("computed override can delegate through multiple parent computeds", () => { @@ -102,16 +95,12 @@ test("computed override can delegate through multiple parent computeds", () => { } const child = new Child() - const seen: number[] = [] - const dispose = observe(child, "number", change => seen.push(change.newValue), true) runInAction(() => { child.count = 2 }) - dispose() expect(child.number).toBe(4) - expect(seen).toEqual([3, 4]) }) // #4660: A parent constructor read must not pin the parent computed as the child property @@ -137,13 +126,9 @@ test("computed override wins when parent constructor reads the same key first", } const child = new Child() - const seen: number[] = [] - const dispose = observe(child, "number", change => seen.push(change.newValue), true) - dispose() expect(child.number).toBe(0) expect(isComputedProp(child, "number")).toBe(true) - expect(seen).toEqual([0]) }) test("manually wrapped action field can be overridden as an ordinary field", () => { diff --git a/packages/mobx/__tests__/base/stage3-decorators.ts b/packages/mobx/__tests__/base/stage3-decorators.ts index df5b1615c..685ef026f 100644 --- a/packages/mobx/__tests__/base/stage3-decorators.ts +++ b/packages/mobx/__tests__/base/stage3-decorators.ts @@ -1,7 +1,6 @@ "use strict" import { - observe, autorun, extendObservable, IObservableArray, @@ -10,7 +9,6 @@ import { isObservableProp, isObservableObject, transaction, - IObjectDidChange, configure, isAction, IAtom, @@ -69,28 +67,6 @@ test("decorators", () => { t.equal(isObservableObject(o), true) t.equal(isObservableProp(o, "amount"), true) t.equal(isObservableProp(o, "total"), true) - - const events: any[] = [] - const d1 = observe(o, (ev: IObjectDidChange) => events.push(ev.name, (ev as any).oldValue)) - const d2 = observe(o, "price", ev => events.push(ev.newValue, ev.oldValue)) - const d3 = observe(o, "total", ev => events.push(ev.newValue, ev.oldValue)) - - o.price = 4 - - d1() - d2() - d3() - - o.price = 5 - - t.deepEqual(events, [ - 8, // new total - 6, // old total - 4, // new price - 3, // old price - "price", // event name - 3 // event oldValue - ]) }) test("annotations", () => { @@ -432,23 +408,6 @@ test("267 (2022.3) should be possible to declare properties observable outside s } }) -test("288 atom not detected for object property", () => { - class Store { - @observable accessor foo = "" - } - - const store = new Store() - - mobx.observe( - store, - "foo", - () => { - // console.log("Change observed") - }, - true - ) -}) - test.skip("observable performance - ts - decorators", () => { const AMOUNT = 100000 @@ -868,15 +827,6 @@ test("@computed.equals (2022.3)", () => { disposeAutorun() }) -test("1072 - @observable accessor without initial value and observe before first access", () => { - class User { - @observable accessor loginCount: number = 0 - } - - const user = new User() - observe(user, "loginCount", () => {}) -}) - test("unobserved computed reads should warn with requiresReaction enabled", () => { const consoleWarn = console.warn const warnings: string[] = [] @@ -1043,34 +993,6 @@ test("toJS bug #1413 (2022.3)", () => { expect(res.__mobxDidRunLazyInitializers).toBe(undefined) }) -test("#2159 - computed property keys", () => { - const testSymbol = Symbol("test symbol") - const testString = "testString" - - class TestClass { - @observable accessor [testSymbol] = "original symbol value" - @observable accessor [testString] = "original string value" - } - - const o = new TestClass() - - const events: any[] = [] - observe(o, testSymbol, ev => events.push(ev.newValue, ev.oldValue)) - observe(o, testString, ev => events.push(ev.newValue, ev.oldValue)) - - runInAction(() => { - o[testSymbol] = "new symbol value" - o[testString] = "new string value" - }) - - t.deepEqual(events, [ - "new symbol value", // new symbol - "original symbol value", // original symbol - "new string value", // new string - "original string value" // original string - ]) -}) - test("4616 - @computed decorator should be lazy", () => { let computeCount = 0 @@ -1125,23 +1047,6 @@ test("4616 - isComputedProp reports lazy @computed before first read", () => { t.equal(isComputedProp(o, "total"), true) }) -test("4616 - observe on @computed before first read materialises it", () => { - class Order { - @observable accessor price: number = 3 - - @computed - get total() { - return this.price * 2 - } - } - - const o = new Order() - const events: number[] = [] - observe(o, "total", ev => events.push((ev as any).newValue)) - o.price = 4 - t.deepEqual(events, [8]) -}) - test("4616 - @observable accessor should be lazy", () => { class Wide { @observable accessor unused: number = 1 @@ -1170,25 +1075,6 @@ test("4616 - @observable accessor should be lazy", () => { expect(adm.lazyObservableKeys_.has("unused")).toBe(true) }) -test("4616 - observe on @observable accessor before first read materialises it", () => { - class Counter { - @observable accessor count: number = 0 - } - - const o = new Counter() - const adm: any = (o as any)[$mobx] - expect(adm.values_.has("count")).toBe(false) - - const events: number[] = [] - observe(o, "count", ev => events.push((ev as any).newValue)) - // observe should have materialised the ObservableValue - expect(adm.values_.has("count")).toBe(true) - - o.count = 5 - o.count = 7 - t.deepEqual(events, [5, 7]) -}) - test("4616 - set on @observable accessor before first read materialises it", () => { class Counter { @observable accessor count: number = 0 diff --git a/packages/mobx/__tests__/base/tojs.js b/packages/mobx/__tests__/base/tojs.js index e4f857433..72fec14ad 100644 --- a/packages/mobx/__tests__/base/tojs.js +++ b/packages/mobx/__tests__/base/tojs.js @@ -69,20 +69,12 @@ test("json2", function () { let ab = [] let tb = [] - m.observe( - analyze, - function (d) { - ab.push(d.newValue) - }, - true - ) - m.observe( - alltags, - function (d) { - tb.push(d.newValue) - }, - true - ) + m.autorun(function () { + ab.push(analyze.get()) + }) + m.autorun(function () { + tb.push(alltags.get()) + }) o.todos[0].details.url = "boe" o.todos[1].details.url = "ba" diff --git a/packages/mobx/__tests__/base/typescript-tests.ts b/packages/mobx/__tests__/base/typescript-tests.ts index 961db4308..740631278 100644 --- a/packages/mobx/__tests__/base/typescript-tests.ts +++ b/packages/mobx/__tests__/base/typescript-tests.ts @@ -1,7 +1,6 @@ "use strict" import { - observe, computed, computedStruct, observable, @@ -12,13 +11,11 @@ import { extendObservable, action, actionBound, - IArrayDidChange, IObservableValue, isObservable, isObservableProp, isObservableObject, transaction, - IObjectDidChange, configure, isAction, makeObservable, @@ -26,17 +23,11 @@ import { createAtom, runInAction, flow, - IMapDidChange, - IValueDidChange, - ISetDidChange, flowResult } from "../../src/mobx" import * as mobx from "../../src/mobx" import { assert, IsExact } from "conditional-type-checks" -const v = observable.box(3) -observe(v, () => {}) - const a = observable([1, 2, 3]) const testFunction = function (a: any) {} @@ -89,28 +80,6 @@ test("decorators", () => { t.equal(isObservableObject(o), true) t.equal(isObservableProp(o, "amount"), true) t.equal(isObservableProp(o, "total"), true) - - const events: any[] = [] - const d1 = observe(o, (ev: IObjectDidChange) => events.push(ev.name, (ev as any).oldValue)) - const d2 = observe(o, "price", ev => events.push(ev.newValue, ev.oldValue)) - const d3 = observe(o, "total", ev => events.push(ev.newValue, ev.oldValue)) - - o.price = 4 - - d1() - d2() - d3() - - o.price = 5 - - t.deepEqual(events, [ - 8, // new total - 6, // old total - 4, // new price - 3, // old price - "price", // event name - 3 // event oldValue - ]) }) test("observable", () => { @@ -595,29 +564,6 @@ test("267 (typescript) should be possible to declare properties observable outsi } }) -test("288 atom not detected for object property", () => { - class Store { - foo = "" - - constructor() { - makeObservable(this, { - foo: observable - }) - } - } - - const store = new Store() - - mobx.observe( - store, - "foo", - () => { - // console.log("Change observed") - }, - true - ) -}) - test.skip("observable performance - ts", () => { const AMOUNT = 100000 @@ -1457,21 +1403,6 @@ test("computed comparer works with decorate (TS) - 3", () => { disposeAutorun() }) -test("1072 - @observable without initial value and observe before first access", () => { - class User { - loginCount?: number - - constructor() { - makeObservable(this, { - loginCount: observable - }) - } - } - - const user = new User() - observe(user, "loginCount", () => {}) -}) - test("typescript - decorate works with classes", () => { class Box { height: number = 2 @@ -1903,41 +1834,6 @@ test("type of flows that return promises", async () => { expect(n).toBe(5) }) -test("#2159 - computed property keys", () => { - const testSymbol = Symbol("test symbol") - const testString = "testString" - - class TestClass { - [testSymbol] = "original symbol value"; - [testString] = "original string value" - - constructor() { - makeObservable(this, { - [testSymbol]: observable, - [testString]: observable - }) - } - } - - const o = new TestClass() - - const events: any[] = [] - observe(o, testSymbol, ev => events.push(ev.newValue, ev.oldValue)) - observe(o, testString, ev => events.push(ev.newValue, ev.oldValue)) - - runInAction(() => { - o[testSymbol] = "new symbol value" - o[testString] = "new string value" - }) - - t.deepEqual(events, [ - "new symbol value", // new symbol - "original symbol value", // original symbol - "new string value", // new string - "original string value" // original string - ]) -}) - test("type inference of the action callback", () => { function test1arg(fn: (a: number) => any) {} diff --git a/packages/mobx/__tests__/perf/perf.js b/packages/mobx/__tests__/perf/perf.js index 08447ab48..1d40ab4ce 100644 --- a/packages/mobx/__tests__/perf/perf.js +++ b/packages/mobx/__tests__/perf/perf.js @@ -5,10 +5,6 @@ function gc() { if (typeof global.gc === "function") global.gc() } -function voidObserver() { - // nothing, nada, noppes. -} - module.exports = function runPerfSuite() { /* results of this test: @@ -49,7 +45,7 @@ results of this test: const start = now() - mobx.observe(b, voidObserver, true) // start observers + mobx.autorun(() => b.get()) // start observers t.equal(99990000, b.get()) const initial = now() @@ -84,7 +80,7 @@ results of this test: const start = now() const last = observables[observables.length - 1] - mobx.observe(last, voidObserver) + mobx.autorun(() => last.get()) t.equal(501, last.get()) const initial = now() @@ -113,7 +109,7 @@ results of this test: return sum }) - mobx.observe(sum, voidObserver, true) + mobx.autorun(() => sum.get()) const start = new Date() @@ -148,13 +144,9 @@ results of this test: }) let sum = 0 - const subscription = mobx.observe( - b, - function (e) { - sum = e.newValue - }, - true - ) + const subscription = mobx.autorun(() => { + sum = b.get() + }) t.equal(sum, 49995000) @@ -206,8 +198,8 @@ results of this test: bCalc++ return ar.lastIndexOf(0) }) - mobx.observe(findLastIndexOfZero, voidObserver, true) - mobx.observe(lastIndexOfZero, voidObserver, true) + mobx.autorun(() => findLastIndexOfZero.get()) + mobx.autorun(() => lastIndexOfZero.get()) const start = now() @@ -238,7 +230,7 @@ results of this test: return a + c * b.get() }, 0) }) - mobx.observe(sum, voidObserver) + mobx.autorun(() => sum.get()) const start = now() @@ -279,7 +271,7 @@ results of this test: for (let i = 0; i < ar.length; i++) s += ar[i] * b.get() return s }) - mobx.observe(sum, voidObserver, true) // calculate + mobx.autorun(() => sum.get()) // calculate const start = now() @@ -353,7 +345,7 @@ results of this test: } let disp - if (keepObserving) disp = mobx.observe(totalAmount, voidObserver) + if (keepObserving) disp = mobx.autorun(() => totalAmount.get()) const start = now() diff --git a/packages/mobx/src/api/observe.ts b/packages/mobx/src/api/observe.ts deleted file mode 100644 index f60240687..000000000 --- a/packages/mobx/src/api/observe.ts +++ /dev/null @@ -1,148 +0,0 @@ -import { - IArrayDidChange, - IComputedValue, - IMapDidChange, - IObjectDidChange, - IObservableArray, - IObservableValue, - IValueDidChange, - Lambda, - ObservableMap, - getAdministration, - ObservableSet, - ISetDidChange, - isFunction, - isComputedValue, - isObservableArray, - isObservableMap, - isObservableObject, - isObservableSet, - autorun, - registerListener, - untrackedEnd, - untrackedStart, - UPDATE, - die -} from "../internal" - -export function observe( - value: IObservableValue | IComputedValue, - listener: (change: IValueDidChange) => void, - fireImmediately?: boolean -): Lambda -export function observe( - observableArray: IObservableArray | Array, - listener: (change: IArrayDidChange) => void, - fireImmediately?: boolean -): Lambda -export function observe( - // ObservableSet/ObservableMap are required despite they implement Set/Map: https://github.com/mobxjs/mobx/pull/3180#discussion_r746542929 - observableSet: ObservableSet | Set, - listener: (change: ISetDidChange) => void, - fireImmediately?: boolean -): Lambda -export function observe( - observableMap: ObservableMap | Map, - listener: (change: IMapDidChange) => void, - fireImmediately?: boolean -): Lambda -export function observe( - observableMap: ObservableMap | Map, - property: K, - listener: (change: IValueDidChange) => void, - fireImmediately?: boolean -): Lambda -export function observe( - object: Object, - listener: (change: IObjectDidChange) => void, - fireImmediately?: boolean -): Lambda -export function observe( - object: T, - property: K, - listener: (change: IValueDidChange) => void, - fireImmediately?: boolean -): Lambda -export function observe(thing, propOrCb?, cbOrFire?, fireImmediately?): Lambda { - if (isFunction(cbOrFire)) { - return observeObservableProperty(thing, propOrCb, cbOrFire, fireImmediately) - } else { - return observeObservable(thing, propOrCb, cbOrFire) - } -} - -function observeObservable(thing, listener, fireImmediately: boolean) { - const adm = getAdministration(thing) - - if (isObservableArray(thing)) { - if (fireImmediately) { - listener({ - observableKind: "array", - object: adm.proxy_, - debugObjectName: adm.atom_.name_, - type: "splice", - index: 0, - added: adm.values_.slice(), - addedCount: adm.values_.length, - removed: [], - removedCount: 0 - }) - } - } else if (isObservableMap(thing)) { - if (__DEV__ && fireImmediately === true) { - die("`observe` doesn't support fireImmediately=true in combination with maps.") - } - } else if (isObservableSet(thing)) { - if (__DEV__ && fireImmediately === true) { - die("`observe` doesn't support fireImmediately=true in combination with sets.") - } - } else if (isObservableObject(thing)) { - if (__DEV__ && fireImmediately === true) { - die("`observe` doesn't support the fire immediately property for observable objects.") - } - } else { - return observeValue(adm, listener, fireImmediately) - } - - return registerListener(adm, listener) -} - -function observeObservableProperty(thing, property, listener, fireImmediately: boolean) { - return observeValue(getAdministration(thing, property), listener, fireImmediately) -} - -function observeValue(adm, listener, fireImmediately: boolean) { - if (isComputedValue(adm)) { - let firstTime = true - let prevValue: any = undefined - return autorun(() => { - const newValue = adm.get() - if (!firstTime || fireImmediately) { - const prevU = untrackedStart() - listener({ - observableKind: "computed", - debugObjectName: adm.name_, - type: UPDATE, - object: adm, - newValue, - oldValue: prevValue - }) - untrackedEnd(prevU) - } - firstTime = false - prevValue = newValue - }) - } - - if (fireImmediately) { - listener({ - observableKind: "value", - debugObjectName: adm.name_, - object: adm, - type: UPDATE, - newValue: adm.value_, - oldValue: undefined - }) - } - return registerListener(adm, listener) -} diff --git a/packages/mobx/src/internal.ts b/packages/mobx/src/internal.ts index c0f963289..c3f7065a3 100644 --- a/packages/mobx/src/internal.ts +++ b/packages/mobx/src/internal.ts @@ -37,12 +37,10 @@ export * from "./api/flow" export * from "./api/iscomputed" export * from "./api/isobservable" export * from "./api/object-api" -export * from "./api/observe" export * from "./api/tojs" export * from "./api/transaction" export * from "./api/when" export * from "./types/dynamicobject" -export * from "./types/listen-utils" export * from "./api/makeObservable" export * from "./types/observablearray" export * from "./types/observablemap" diff --git a/packages/mobx/src/mobx.ts b/packages/mobx/src/mobx.ts index c751bcaee..509ebaf1b 100644 --- a/packages/mobx/src/mobx.ts +++ b/packages/mobx/src/mobx.ts @@ -41,27 +41,19 @@ export { compareStructural, compareShallow, IEnhancer, - IListenable, - IObjectDidChange, isObservableObject, - IValueDidChange, IObservableValue, isObservableValue as isBoxedObservable, IObservableArray, - IArraySplice, - IArrayUpdate, - IArrayDidChange, isObservableArray, IKeyValueMap, ObservableMap, IMapEntries, IMapEntry, - IMapDidChange, isObservableMap, IObservableMapInitialValues, ObservableSet, isObservableSet, - ISetDidChange, IObservableSetInitialValues, transaction, observable, @@ -79,7 +71,6 @@ export { isComputed, isComputedProp, extendObservable, - observe, autorun, IAutorunOptions, reaction, diff --git a/packages/mobx/src/types/listen-utils.ts b/packages/mobx/src/types/listen-utils.ts deleted file mode 100644 index 453390aea..000000000 --- a/packages/mobx/src/types/listen-utils.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { Lambda, once, untrackedEnd, untrackedStart } from "../internal" - -export interface IListenable { - changeListeners_: Function[] | undefined -} - -export function hasListeners(listenable: IListenable) { - return listenable.changeListeners_ !== undefined && listenable.changeListeners_.length > 0 -} - -export function registerListener(listenable: IListenable, handler: Function): Lambda { - const listeners = listenable.changeListeners_ || (listenable.changeListeners_ = []) - listeners.push(handler) - return once(() => { - const idx = listeners.indexOf(handler) - if (idx !== -1) { - listeners.splice(idx, 1) - } - }) -} - -export function notifyListeners(listenable: IListenable, change: T) { - const prevU = untrackedStart() - let listeners = listenable.changeListeners_ - if (!listeners) { - return - } - listeners = listeners.slice() - for (let i = 0, l = listeners.length; i < l; i++) { - listeners[i](change) - } - untrackedEnd(prevU) -} diff --git a/packages/mobx/src/types/observablearray.ts b/packages/mobx/src/types/observablearray.ts index f6e171a73..1744a55dc 100644 --- a/packages/mobx/src/types/observablearray.ts +++ b/packages/mobx/src/types/observablearray.ts @@ -4,22 +4,17 @@ import { EMPTY_ARRAY, IAtom, IEnhancer, - IListenable, addHiddenFinalProp, checkIfStateModificationsAreAllowed, createInstanceofPredicate, getNextId, - hasListeners, isObject, - notifyListeners, hasProp, die, globalState, initObservable } from "../internal" -const SPLICE = "splice" -export const UPDATE = "update" export const MAX_SPLICE_SIZE = 10000 // See e.g. https://github.com/mobxjs/mobx/issues/859 export interface IObservableArray extends Array { @@ -30,29 +25,6 @@ export interface IObservableArray extends Array { toJSON(): T[] } -interface IArrayBaseChange { - object: IObservableArray - observableKind: "array" - debugObjectName: string - index: number -} - -export type IArrayDidChange = IArrayUpdate | IArraySplice - -export interface IArrayUpdate extends IArrayBaseChange { - type: "update" - newValue: T - oldValue: T -} - -export interface IArraySplice extends IArrayBaseChange { - type: "splice" - added: T[] - addedCount: number - removed: T[] - removedCount: number -} - const arrayTraps = { get(target, name) { const adm: ObservableArrayAdministration = target[$mobx] @@ -88,10 +60,9 @@ const arrayTraps = { } } -export class ObservableArrayAdministration implements IListenable { +export class ObservableArrayAdministration { atom_: IAtom readonly values_: any[] = [] // this is the prop that gets proxied, so can't replace it! - changeListeners_ enhancer_: (newV: any, oldV: any | undefined) => any proxy_!: IObservableArray lastKnownLength_ = 0 @@ -166,7 +137,7 @@ export class ObservableArrayAdministration implements IListenable { const res = this.spliceItemsIntoValues_(index, deleteCount, newItems) if (deleteCount !== 0 || newItems.length !== 0) { - this.notifyArraySplice_(index, newItems, res) + this.atom_.reportChanged() } return res } @@ -191,49 +162,6 @@ export class ObservableArrayAdministration implements IListenable { } } - notifyArrayChildUpdate_(index: number, newValue: any, oldValue: any) { - const notify = hasListeners(this) - const change: IArrayDidChange | null = notify - ? ({ - observableKind: "array", - object: this.proxy_, - type: UPDATE, - debugObjectName: this.atom_.name_, - index, - newValue, - oldValue - } as const) - : null - - this.atom_.reportChanged() - if (notify) { - notifyListeners(this, change) - } - } - - notifyArraySplice_(index: number, added: any[], removed: any[]) { - const notify = hasListeners(this) - const change: IArraySplice | null = notify - ? ({ - observableKind: "array", - object: this.proxy_, - debugObjectName: this.atom_.name_, - type: SPLICE, - index, - removed, - added, - removedCount: removed.length, - addedCount: added.length - } as const) - : null - - this.atom_.reportChanged() - // conform: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/observe - if (notify) { - notifyListeners(this, change) - } - } - get_(index: number): any | undefined { this.atom_.reportObserved() return this.values_[index] @@ -249,7 +177,7 @@ export class ObservableArrayAdministration implements IListenable { const changed = newValue !== oldValue if (changed) { values[index] = newValue - this.notifyArrayChildUpdate_(index, newValue, oldValue) + this.atom_.reportChanged() } } else { // For out of bound index, we don't create an actual sparse array, diff --git a/packages/mobx/src/types/observablemap.ts b/packages/mobx/src/types/observablemap.ts index 0f1c3050c..fab6c94f1 100644 --- a/packages/mobx/src/types/observablemap.ts +++ b/packages/mobx/src/types/observablemap.ts @@ -1,7 +1,6 @@ import { $mobx, IEnhancer, - IListenable, ObservableValue, checkIfStateModificationsAreAllowed, createAtom, @@ -10,18 +9,15 @@ import { deepEnhancer, getNextId, getPlainObjectKeys, - hasListeners, isES6Map, isPlainES6Map, isPlainObject, - notifyListeners, referenceEnhancer, stringifyKey, transaction, untracked, globalState, die, - UPDATE, IAtom, initObservable } from "../internal" @@ -35,33 +31,8 @@ export type IReadonlyMapEntry = readonly [K, V] export type IMapEntries = IMapEntry[] export type IReadonlyMapEntries = readonly IReadonlyMapEntry[] -export type IMapDidChange = { observableKind: "map"; debugObjectName: string } & ( - | { - object: ObservableMap - name: K // actual the key or index, but this is based on the ancient .observe proposal for consistency - type: "update" - newValue: V - oldValue: V - } - | { - object: ObservableMap - name: K - type: "add" - newValue: V - } - | { - object: ObservableMap - name: K - type: "delete" - oldValue: V - } -) - const ObservableMapMarker = {} -export const ADD = "add" -export const DELETE = "delete" - export type IObservableMapInitialValues = | IMapEntries | IReadonlyMapEntries @@ -70,12 +41,11 @@ export type IObservableMapInitialValues = // just extend Map? See also https://gist.github.com/nestharus/13b4d74f2ef4a2f4357dbd3fc23c1e54 // But: https://github.com/mobxjs/mobx/issues/1556 -export class ObservableMap implements Map, IListenable { +export class ObservableMap implements Map { [$mobx] = ObservableMapMarker data_!: Map> hasMap_!: Map> // hasMap, not hashMap >-). keysAtom_!: IAtom - changeListeners_ constructor( initialData?: IObservableMapInitialValues, @@ -128,18 +98,6 @@ export class ObservableMap implements Map, IListenable { delete(key: K): boolean { checkIfStateModificationsAreAllowed(this.keysAtom_) if (this.has_(key)) { - const notify = hasListeners(this) - const change: IMapDidChange | null = notify - ? { - observableKind: "map", - debugObjectName: this.name_, - type: DELETE, - object: this, - oldValue: (this.data_.get(key)).value_, - name: key - } - : null - transaction(() => { this.keysAtom_.reportChanged() this.hasMap_.get(key)?.setNewValue_(false) @@ -147,9 +105,6 @@ export class ObservableMap implements Map, IListenable { observable.setNewValue_(undefined as any) this.data_.delete(key) }) - if (notify) { - notifyListeners(this, change) - } return true } return false @@ -159,22 +114,7 @@ export class ObservableMap implements Map, IListenable { const observable = this.data_.get(key)! newValue = (observable as any).prepareNewValue_(newValue) as V if (newValue !== globalState.UNCHANGED) { - const notify = hasListeners(this) - const change: IMapDidChange | null = notify - ? { - observableKind: "map", - debugObjectName: this.name_, - type: UPDATE, - object: this, - oldValue: (observable as any).value_, - name: key, - newValue - } - : null observable.setNewValue_(newValue as V) - if (notify) { - notifyListeners(this, change) - } } } @@ -187,24 +127,9 @@ export class ObservableMap implements Map, IListenable { __DEV__ ? `${this.name_}.${stringifyKey(key)}` : "ObservableMap.key" ) this.data_.set(key, observable) - newValue = (observable as any).value_ // value might have been changed this.hasMap_.get(key)?.setNewValue_(true) this.keysAtom_.reportChanged() }) - const notify = hasListeners(this) - const change: IMapDidChange | null = notify - ? { - observableKind: "map", - debugObjectName: this.name_, - type: ADD, - object: this, - name: key, - newValue - } - : null - if (notify) { - notifyListeners(this, change) - } } get(key: K): V | undefined { diff --git a/packages/mobx/src/types/observableobject.ts b/packages/mobx/src/types/observableobject.ts index 474a812d8..988d3d93d 100644 --- a/packages/mobx/src/types/observableobject.ts +++ b/packages/mobx/src/types/observableobject.ts @@ -10,25 +10,19 @@ import { IAtom, IComputedValueOptions, IEnhancer, - IListenable, ObservableValue, addHiddenProp, createInstanceofPredicate, endBatch, getNextId, - hasListeners, isObject, isPlainObject, - notifyListeners, referenceEnhancer, startBatch, stringifyKey, globalState, - ADD, - UPDATE, die, hasProp, - getDescriptor, ownKeys, isOverride, defineProperty, @@ -40,32 +34,8 @@ import { const descriptorCache = Object.create(null) -export type IObjectDidChange = { - observableKind: "object" - name: PropertyKey - object: T - debugObjectName: string -} & ( - | { - type: "add" - newValue: any - } - | { - type: "update" - oldValue: any - newValue: any - } - | { - type: "remove" - oldValue: any - } -) - -const REMOVE = "remove" - -export class ObservableObjectAdministration implements IListenable { +export class ObservableObjectAdministration { keysAtom_: IAtom - changeListeners_ proxy_: any isPlainObject_: boolean appliedAnnotations_?: object @@ -143,23 +113,7 @@ export class ObservableObjectAdministration implements IListenable { // notify observers if (newValue !== globalState.UNCHANGED) { - const notify = hasListeners(this) - const change: IObjectDidChange | null = notify - ? { - type: UPDATE, - observableKind: "object", - debugObjectName: this.name_, - object: this.proxy_ || this.target_, - oldValue: (observable as any).value_, - name: key, - newValue - } - : null - ;(observable as ObservableValue).setNewValue_(newValue) - if (notify) { - notifyListeners(this, change) - } } return true } @@ -283,7 +237,7 @@ export class ObservableObjectAdministration implements IListenable { } // Notify - this.notifyPropertyAddition_(key, descriptor.value) + this.notifyPropertyAddition_(key) } finally { endBatch() } @@ -334,7 +288,7 @@ export class ObservableObjectAdministration implements IListenable { this.values_.set(key, observable) // Notify (value possibly changed by ObservableValue) - this.notifyPropertyAddition_(key, observable.value_) + this.notifyPropertyAddition_(key) } finally { endBatch() } @@ -380,7 +334,7 @@ export class ObservableObjectAdministration implements IListenable { this.values_.set(key, new ComputedValue(options)) // Notify - this.notifyPropertyAddition_(key, undefined) + this.notifyPropertyAddition_(key) } finally { endBatch() } @@ -403,14 +357,7 @@ export class ObservableObjectAdministration implements IListenable { // Delete try { startBatch() - const notify = hasListeners(this) const observable = this.values_.get(key) - // Value needed for listeners - let value = undefined - // Optimization: don't pull the value unless we will need it - if (!observable && notify) { - value = getDescriptor(this.target_, key)?.value - } // delete prop (do first, may fail) if (proxyTrap) { if (!Reflect.deleteProperty(this.target_, key)) { @@ -426,10 +373,6 @@ export class ObservableObjectAdministration implements IListenable { // Clear observable if (observable) { this.values_.delete(key) - // for computed, value is undefined - if (observable instanceof ObservableValue) { - value = observable.value_ - } // Notify: autorun(() => obj[key]), see #1796 propagateChanged(observable) } @@ -439,39 +382,13 @@ export class ObservableObjectAdministration implements IListenable { // Notify "has" observers // "in" as it may still exist in proto this.pendingKeys_?.get(key)?.set(key in this.target_) - - // Notify listeners - if (notify) { - const change: IObjectDidChange = { - type: REMOVE, - observableKind: "object", - object: this.proxy_ || this.target_, - debugObjectName: this.name_, - oldValue: value, - name: key - } - notifyListeners(this, change) - } } finally { endBatch() } return true } - notifyPropertyAddition_(key: PropertyKey, value: any) { - const notify = hasListeners(this) - if (notify) { - const change: IObjectDidChange = { - type: ADD, - observableKind: "object", - debugObjectName: this.name_, - object: this.proxy_ || this.target_, - name: key, - newValue: value - } - notifyListeners(this, change) - } - + notifyPropertyAddition_(key: PropertyKey) { this.pendingKeys_?.get(key)?.set(true) // Notify "keys/entries/values" observers diff --git a/packages/mobx/src/types/observableset.ts b/packages/mobx/src/types/observableset.ts index d8764a29c..a5a480e48 100644 --- a/packages/mobx/src/types/observableset.ts +++ b/packages/mobx/src/types/observableset.ts @@ -4,9 +4,6 @@ import { deepEnhancer, getNextId, IEnhancer, - hasListeners, - IListenable, - notifyListeners, createInstanceofPredicate, makeIterable, checkIfStateModificationsAreAllowed, @@ -14,8 +11,6 @@ import { transaction, isES6Set, IAtom, - DELETE, - ADD, die, initObservable } from "../internal" @@ -24,27 +19,10 @@ const ObservableSetMarker = {} export type IObservableSetInitialValues = Set | readonly T[] -export type ISetDidChange = - | { - object: ObservableSet - observableKind: "set" - debugObjectName: string - type: "add" - newValue: T - } - | { - object: ObservableSet - observableKind: "set" - debugObjectName: string - type: "delete" - oldValue: T - } - -export class ObservableSet implements Set, IListenable { +export class ObservableSet implements Set { [$mobx] = ObservableSetMarker private data_: Set = new Set() atom_!: IAtom - changeListeners_ enhancer_: (newV: any, oldV: any | undefined) => any constructor( @@ -89,19 +67,6 @@ export class ObservableSet implements Set, IListenable { this.data_.add(this.enhancer_(value, undefined)) this.atom_.reportChanged() }) - const notify = hasListeners(this) - const change = notify - ? >{ - observableKind: "set", - debugObjectName: this.name_, - type: ADD, - object: this, - newValue: value - } - : null - if (notify) { - notifyListeners(this, change) - } } return this @@ -109,24 +74,10 @@ export class ObservableSet implements Set, IListenable { delete(value: T) { if (this.has(value)) { - const notify = hasListeners(this) - const change = notify - ? >{ - observableKind: "set", - debugObjectName: this.name_, - type: DELETE, - object: this, - oldValue: value - } - : null - transaction(() => { this.atom_.reportChanged() this.data_.delete(value) }) - if (notify) { - notifyListeners(this, change) - } return true } return false diff --git a/packages/mobx/src/types/observablevalue.ts b/packages/mobx/src/types/observablevalue.ts index 7224104c6..bfacb65c8 100644 --- a/packages/mobx/src/types/observablevalue.ts +++ b/packages/mobx/src/types/observablevalue.ts @@ -2,35 +2,22 @@ import { Atom, IEnhancer, IEqualsComparer, - IListenable, checkIfStateModificationsAreAllowed, compareDefault, createInstanceofPredicate, getNextId, - hasListeners, - notifyListeners, toPrimitive, globalState, - IUNCHANGED, - UPDATE + IUNCHANGED } from "../internal" -export type IValueDidChange = { - type: "update" - observableKind: "value" - object: IObservableValue - debugObjectName: string - newValue: T - oldValue: T | undefined -} export interface IObservableValue { get(): T set(value: T): void } -export class ObservableValue extends Atom implements IObservableValue, IListenable { +export class ObservableValue extends Atom implements IObservableValue { hasUnreportedChange_ = false - changeListeners_ value_ constructor( @@ -58,17 +45,8 @@ export class ObservableValue extends Atom implements IObservableValue, ILi } setNewValue_(newValue: T) { - const oldValue = this.value_ this.value_ = newValue this.reportChanged() - if (hasListeners(this)) { - notifyListeners(this, { - type: UPDATE, - object: this, - newValue, - oldValue - }) - } } public get(): T { diff --git a/website/i18n/en.json b/website/i18n/en.json index 67b75a7e6..ad085de42 100644 --- a/website/i18n/en.json +++ b/website/i18n/en.json @@ -75,10 +75,6 @@ "title": "Installation", "sidebar_label": "Installation" }, - "intercept-and-observe": { - "title": "Intercept & Observe", - "sidebar_label": "Intercept & Observe {🚀}" - }, "intro/concepts": { "title": "The gist of MobX" }, diff --git a/website/sidebars.json b/website/sidebars.json index c561a7524..4c8623e72 100755 --- a/website/sidebars.json +++ b/website/sidebars.json @@ -27,8 +27,7 @@ "mobx-utils", "custom-observables", "lazy-observables", - "collection-utilities", - "intercept-and-observe" + "collection-utilities" ], "Fine-tuning": [ "configuration", From abda45c0558a305281e573dafffd844e940570b7 Mon Sep 17 00:00:00 2001 From: Michel Weststrate Date: Thu, 30 Jul 2026 22:56:56 +0200 Subject: [PATCH 07/10] refactor(mobx): remove top-level object API (get/set/has/remove/keys/values/entries/ownKeys) --- docs/api.md | 55 -- docs/collection-utilities.md | 48 -- packages/mobx/__tests__/base/api.js | 8 - packages/mobx/__tests__/base/map.js | 34 +- .../mobx/__tests__/base/object-api-proxy.js | 5 +- packages/mobx/__tests__/base/object-api.js | 571 ------------------ packages/mobx/__tests__/base/proxies.js | 3 - packages/mobx/__tests__/base/set.js | 22 +- .../mobx/__tests__/base/typescript-tests.ts | 2 +- packages/mobx/src/api/object-api.ts | 182 +----- packages/mobx/src/api/tojs.ts | 4 +- packages/mobx/src/errors.ts | 11 - packages/mobx/src/mobx.ts | 8 - website/i18n/en.json | 4 - website/sidebars.json | 3 +- 15 files changed, 35 insertions(+), 925 deletions(-) delete mode 100644 docs/collection-utilities.md delete mode 100644 packages/mobx/__tests__/base/object-api.js diff --git a/docs/api.md b/docs/api.md index be2524bd6..7b6ec1bcb 100644 --- a/docs/api.md +++ b/docs/api.md @@ -329,61 +329,6 @@ Use it to change how MobX behaves as a whole. --- -## Collection utilities {🚀} - -_They enable manipulating observable arrays, objects and Maps with the same generic API._ - -### `values` - -{🚀} Usage: `values(array|object|Set|Map)` -([further information](collection-utilities.md)) - -Returns all values in the collection as an array. - -### `keys` - -{🚀} Usage: `keys(array|object|Set|Map)` -([further information](collection-utilities.md)) - -Returns all keys / indices in the collection as an array. - -### `entries` - -{🚀} Usage: `entries(array|object|Set|Map)` -([further information](collection-utilities.md)) - -Returns a `[key, value]` pair of every entry in the collection as an array. - -### `set` - -{🚀} Usage: `set(array|object|Map, key, value)` -([further information](collection-utilities.md)) - -Updates the collection. - -### `remove` - -{🚀} Usage: `remove(array|object|Map, key)` -([further information](collection-utilities.md)) - -Removes item from the collection. - -### `has` - -{🚀} Usage: `has(array|object|Map, key)` -([further information](collection-utilities.md)) - -Checks for membership in the collection. - -### `get` - -{🚀} Usage: `get(array|object|Map, key)` -([further information](collection-utilities.md)) - -Gets value from the collection with key. - ---- - ## Introspection utilities {🚀} _Utilities that might come in handy if you want to inspect the internal state of MobX, or want to build cool tools on top of MobX._ diff --git a/docs/collection-utilities.md b/docs/collection-utilities.md deleted file mode 100644 index d4bf7894b..000000000 --- a/docs/collection-utilities.md +++ /dev/null @@ -1,48 +0,0 @@ ---- -title: Collection utilities -sidebar_label: Collection utilities {🚀} -hide_title: true ---- - - - -# Collection utilities {🚀} - -They enable manipulating observable arrays, objects and Maps with the same generic API. -These APIs are fully reactive and can track keys, values and entries without depending on the concrete collection type. - -Another benefit of `values`, `keys` and `entries` is that they return arrays rather than iterators, which makes it possible to, for example, immediately call `.map(fn)` on the results. - -All that being said, a typical project has little reason to use these APIs. - -Access: - -- `values(collection)` returns an array of all the values in the collection. -- `keys(collection)` returns an array of all the keys in the collection. -- `entries(collection)` returns an array of all the entries `[key, value]` pairs in the collection. - -Mutation: - -- `set(collection, key, value)` or `set(collection, { key: value })` update the given collection with the provided key / value pair(s). -- `remove(collection, key)` removes the specified child from the collection. Splicing is used for arrays. -- `has(collection, key)` returns _true_ if the collection has the specified _observable_ property. -- `get(collection, key)` returns the child under the specified key. - -```javascript -import { autorun, get, set, observable, values } from "mobx" - -const twitterUrls = observable.object({ - Joe: "twitter.com/joey" -}) - -autorun(() => { - // Get can track not yet existing properties. - console.log(get(twitterUrls, "Sara")) -}) - -autorun(() => { - console.log("All urls: " + values(twitterUrls).join(", ")) -}) - -set(twitterUrls, { Sara: "twitter.com/horsejs" }) -``` diff --git a/packages/mobx/__tests__/base/api.js b/packages/mobx/__tests__/base/api.js index ba619972f..b9cdd6e91 100644 --- a/packages/mobx/__tests__/base/api.js +++ b/packages/mobx/__tests__/base/api.js @@ -33,12 +33,10 @@ test("correct api should be exposed", function () { "flowResult", "FlowCancellationError", "isFlowCancellationError", - "get", "_getAdministration", "getAtom", "getDebugName", "getDependencyTree", - "has", "_getGlobalState", "getObserverTree", "isAction", @@ -52,7 +50,6 @@ test("correct api should be exposed", function () { "isObservableSet", "isObservableObject", "isObservableProp", - "keys", "makeAutoObservable", "makeObservable", "ObservableMap", @@ -65,18 +62,13 @@ test("correct api should be exposed", function () { "onReactionError", "onBecomeObserved", "onBecomeUnobserved", - "ownKeys", "Reaction", "reaction", - "remove", "_resetGlobalState", "runInAction", - "set", "toJS", "transaction", "untracked", - "values", - "entries", "when", "_startAction", "_endAction", diff --git a/packages/mobx/__tests__/base/map.js b/packages/mobx/__tests__/base/map.js index d1c7d6515..e5e24166a 100644 --- a/packages/mobx/__tests__/base/map.js +++ b/packages/mobx/__tests__/base/map.js @@ -46,8 +46,8 @@ test("map crud", function () { expect(m.get(s)).toBe("symbol-value") expect(m.get(s.toString())).toBe(undefined) - expect(mobx.keys(m)).toEqual(["1", 1, k, s]) - expect(mobx.values(m)).toEqual(["aa", "b", "arrVal", "symbol-value"]) + expect([...m.keys()]).toEqual(["1", 1, k, s]) + expect([...m.values()]).toEqual(["aa", "b", "arrVal", "symbol-value"]) expect(Array.from(m)).toEqual([ ["1", "aa"], [1, "b"], @@ -69,8 +69,8 @@ test("map crud", function () { expect(m.size).toBe(4) m.clear() - expect(mobx.keys(m)).toEqual([]) - expect(mobx.values(m)).toEqual([]) + expect([...m.keys()]).toEqual([]) + expect([...m.values()]).toEqual([]) expect(m.toJSON()).toEqual([]) expect(m.size).toBe(0) @@ -139,7 +139,7 @@ test("observe value", function () { a.replace({ y: "stuff", z: "zoef" }) expect(valueY).toBe("stuff") - expect(mobx.keys(a)).toEqual(["y", "z"]) + expect([...a.keys()]).toEqual(["y", "z"]) }) test("initialize with entries", function () { @@ -173,7 +173,7 @@ test("observe collections", function () { let keys, values, entries autorun(function () { - keys = mobx.keys(x) + keys = [...x.keys()] }) autorun(function () { values = iteratorToArray(x.values()) @@ -307,7 +307,7 @@ test("issue 119 - unobserve before delete", function () { }) // the error only happens if the value is observed mobx.autorun(function () { - mobx.values(myObservable.myMap).forEach(function (value) { + ;[...myObservable.myMap.values()].forEach(function (value) { propValues.push(value.myCalculatedProp) }) }) @@ -335,7 +335,7 @@ test("map modifier", () => { expect(x.get("a")).toBe(1) x = mobx.observable.map() - expect(mobx.keys(x)).toEqual([]) + expect([...x.keys()]).toEqual([]) x = mobx.observable({ a: mobx.observable.map({ b: { c: 3 } }) }) expect(mobx.isObservableObject(x)).toBe(true) @@ -389,15 +389,15 @@ test("256, map.merge should be not be tracked for target", () => { }) expect(c).toBe(1) - expect(mobx.keys(x)).toEqual(["a", "b"]) + expect([...x.keys()]).toEqual(["a", "b"]) y.set("c", 4) expect(c).toBe(2) - expect(mobx.keys(x)).toEqual(["a", "b", "c"]) + expect([...x.keys()]).toEqual(["a", "b", "c"]) x.set("d", 5) expect(c).toBe(2) - expect(mobx.keys(x)).toEqual(["a", "b", "c", "d"]) + expect([...x.keys()]).toEqual(["a", "b", "c", "d"]) d() }) @@ -406,27 +406,27 @@ test("308, map keys should be coerced to strings correctly", () => { const m = mobx.observable.map() m.set(1, true) m.delete(1) - expect(mobx.keys(m)).toEqual([]) + expect([...m.keys()]).toEqual([]) m.set(1, true) m.set("1", false) m.set(0, true) m.set(-0, false) - expect(Array.from(mobx.keys(m))).toEqual([1, "1", 0]) + expect(Array.from([...m.keys()])).toEqual([1, "1", 0]) expect(m.get(-0)).toBe(false) expect(m.get(1)).toBe(true) m.delete("1") - expect(Array.from(mobx.keys(m))).toEqual([1, 0]) + expect(Array.from([...m.keys()])).toEqual([1, 0]) m.delete(1) - expect(mobx.keys(m)).toEqual([0]) + expect([...m.keys()]).toEqual([0]) m.set(true, true) expect(m.get("true")).toBe(undefined) expect(m.get(true)).toBe(true) m.delete(true) - expect(mobx.keys(m)).toEqual([0]) + expect([...m.keys()]).toEqual([0]) }) test("map should support iterall / iterable ", () => { @@ -649,7 +649,7 @@ test("issue 940, should not be possible to change maps outside strict mode", () mobx.configure({ enforceActions: "observed" }) const m = mobx.observable.map() - const d = mobx.autorun(() => mobx.values(m)) + const d = mobx.autorun(() => [...m.values()]) expect( grabConsole(() => { diff --git a/packages/mobx/__tests__/base/object-api-proxy.js b/packages/mobx/__tests__/base/object-api-proxy.js index c69ecae8a..f5b9285c4 100644 --- a/packages/mobx/__tests__/base/object-api-proxy.js +++ b/packages/mobx/__tests__/base/object-api-proxy.js @@ -1,5 +1,5 @@ const mobx = require("../../src/mobx") -const { has, autorun, when, runInAction, reaction, observable } = mobx +const { autorun, when, runInAction, reaction, observable } = mobx test("keys should be observable when extending", () => { const todos = observable({}) @@ -215,8 +215,7 @@ test("#1739 - delete and undelete should work", () => { const events = [] autorun(() => { - // events.push("a" in x) - events.push(has(x, "a")) + events.push("a" in x) }) x.a = 1 diff --git a/packages/mobx/__tests__/base/object-api.js b/packages/mobx/__tests__/base/object-api.js deleted file mode 100644 index a98921c87..000000000 --- a/packages/mobx/__tests__/base/object-api.js +++ /dev/null @@ -1,571 +0,0 @@ -const mobx = require("../../src/mobx") -const { autorun, keys, when, set, remove, values, entries, reaction, observable, has, get } = mobx - -test("keys should be observable when extending", () => { - const todos = observable({}) - - const todoTitles = [] - reaction( - () => keys(todos).map(key => `${key}: ${todos[key]}`), - titles => todoTitles.push(titles.join(",")) - ) - - mobx.set(todos, { - lewis: "Read Lewis", - chesterton: "Be mind blown by Chesterton" - }) - expect(todoTitles).toEqual(["lewis: Read Lewis,chesterton: Be mind blown by Chesterton"]) - - mobx.set(todos, { lewis: "Read Lewis twice" }) - mobx.set(todos, { coffee: "Grab coffee" }) - expect(todoTitles).toEqual([ - "lewis: Read Lewis,chesterton: Be mind blown by Chesterton", - "lewis: Read Lewis twice,chesterton: Be mind blown by Chesterton", - "lewis: Read Lewis twice,chesterton: Be mind blown by Chesterton,coffee: Grab coffee" - ]) -}) - -test("toJS respects key changes", () => { - const todos = observable({}) - - const serialized = [] - mobx.autorun(() => { - serialized.push(JSON.stringify(mobx.toJS(todos))) - }) - - mobx.set(todos, { - lewis: "Read Lewis", - chesterton: "Be mind blown by Chesterton" - }) - mobx.set(todos, { lewis: "Read Lewis twice" }) - mobx.set(todos, { coffee: "Grab coffee" }) - expect(serialized).toEqual([ - "{}", - '{"lewis":"Read Lewis","chesterton":"Be mind blown by Chesterton"}', - '{"lewis":"Read Lewis twice","chesterton":"Be mind blown by Chesterton"}', - '{"lewis":"Read Lewis twice","chesterton":"Be mind blown by Chesterton","coffee":"Grab coffee"}' - ]) -}) - -test("keys(object), values(object), entries(object)", () => { - const todos = observable({}) - const plain = {} - const keysSnapshots = [] - const valuesSnapshots = [] - const entriesSnapshots = [] - const expectedKeysSnapshots = [] - const expectedValuesSnapshots = [] - const expectedEntriesSnapshots = [] - - const s1 = Symbol() - const s2 = Symbol() - - function expectEquality() { - expect(todos).toEqual(plain) - } - - function expectKeysReaction() { - expectedKeysSnapshots.push(Object.keys(plain)) - } - - function expectValuesReaction() { - expectedValuesSnapshots.push(Object.values(plain)) - } - - function expectEntriesReaction() { - expectedEntriesSnapshots.push(Object.entries(plain)) - } - - reaction( - () => keys(todos), - result => keysSnapshots.push(result) - ) - - reaction( - () => values(todos), - result => valuesSnapshots.push(result) - ) - - reaction( - () => entries(todos), - result => entriesSnapshots.push(result) - ) - - expectEquality() - // add - set(todos, "k1", 1) - plain["k1"] = 1 - expectEquality() - expectKeysReaction() - expectValuesReaction() - expectEntriesReaction() - // add symbol - set(todos, s1, 2) - plain[s1] = 2 - expectEquality() - // see ObservableObjectAdministration.keys() for explanation - expectKeysReaction() - expectValuesReaction() - expectEntriesReaction() - // delete non-existent - remove(todos, "-") - delete plain["-"] - expectEquality() - // delete non-existent symbol - remove(todos, Symbol()) - delete plain[Symbol()] - expectEquality() - // add second - set(todos, "k2", 3) - plain["k2"] = 3 - expectEquality() - expectKeysReaction() - expectValuesReaction() - expectEntriesReaction() - // add second symbol - set(todos, s2, 4) - plain[s2] = 4 - expectEquality() - // see ObservableObjectAdministration.keys() for explanation - expectKeysReaction() - expectValuesReaction() - expectEntriesReaction() - // update - set(todos, "k1", 11) - plain["k1"] = 11 - expectEquality() - expectValuesReaction() - expectEntriesReaction() - // update symbol - set(todos, s1, 22) - plain[s1] = 22 - expectEquality() - // delete - remove(todos, "k1") - delete plain["k1"] - expectEquality() - expectKeysReaction() - expectValuesReaction() - expectEntriesReaction() - // delete symbol - remove(todos, s1) - delete plain[s1] - expectEquality() - // see ObservableObjectAdministration.keys() for explanation - expectKeysReaction() - expectValuesReaction() - expectEntriesReaction() - - expect(keysSnapshots).toEqual(expectedKeysSnapshots) - expect(valuesSnapshots).toEqual(expectedValuesSnapshots) - expect(entriesSnapshots).toEqual(expectedEntriesSnapshots) -}) - -test("values(map)", () => { - const todos = observable.map({}) - const snapshots = [] - - reaction( - () => values(todos), - values => snapshots.push(values) - ) - - expect(has(todos, "x")).toBe(false) - expect(get(todos, "x")).toBe(undefined) - set(todos, "x", 3) - expect(has(todos, "x")).toBe(true) - expect(get(todos, "x")).toBe(3) - remove(todos, "y") - set(todos, "z", 4) - set(todos, "x", 5) - remove(todos, "z") - - expect(snapshots).toEqual([[3], [3, 4], [5, 4], [5]]) -}) - -test("values(map) - symbols", () => { - const todos = observable.map({}) - const snapshots = [] - const x = Symbol() - const y = Symbol() - const z = Symbol("z") - - reaction( - () => values(todos), - values => snapshots.push(values) - ) - - expect(has(todos, x)).toBe(false) - expect(get(todos, x)).toBe(undefined) - set(todos, x, 3) - expect(has(todos, x)).toBe(true) - expect(get(todos, x)).toBe(3) - remove(todos, y) - set(todos, z, 4) - set(todos, x, 5) - remove(todos, z) - - expect(snapshots).toEqual([[3], [3, 4], [5, 4], [5]]) -}) - -test("entries(map)", () => { - const todos = observable.map({}) - const snapshots = [] - - reaction( - () => entries(todos), - entries => snapshots.push(entries) - ) - - expect(has(todos, "x")).toBe(false) - expect(get(todos, "x")).toBe(undefined) - set(todos, "x", 3) - expect(has(todos, "x")).toBe(true) - expect(get(todos, "x")).toBe(3) - remove(todos, "y") - set(todos, "z", 4) - set(todos, "x", 5) - remove(todos, "z") - - expect(snapshots).toEqual([ - [["x", 3]], - [ - ["x", 3], - ["z", 4] - ], - [ - ["x", 5], - ["z", 4] - ], - [["x", 5]] - ]) -}) - -test("entries(map) - symbols", () => { - const todos = observable.map({}) - const snapshots = [] - const x = Symbol() - const y = Symbol() - const z = Symbol("z") - - reaction( - () => entries(todos), - entries => snapshots.push(entries) - ) - - expect(has(todos, x)).toBe(false) - expect(get(todos, x)).toBe(undefined) - set(todos, x, 3) - expect(has(todos, x)).toBe(true) - expect(get(todos, x)).toBe(3) - remove(todos, y) - set(todos, z, 4) - set(todos, x, 5) - remove(todos, z) - - expect(snapshots).toEqual([ - [[x, 3]], - [ - [x, 3], - [z, 4] - ], - [ - [x, 5], - [z, 4] - ], - [[x, 5]] - ]) -}) - -test("keys(map)", () => { - const todos = observable.map({ a: 3 }) - const snapshots = [] - - reaction( - () => keys(todos), - keys => snapshots.push(keys) - ) - - set(todos, "x", 3) - remove(todos, "y") - set(todos, "z", 4) - set(todos, "x", 5) - remove(todos, "z") - remove(todos, "a") - - expect(snapshots).toEqual([["a", "x"], ["a", "x", "z"], ["a", "x"], ["x"]]) -}) - -test("keys(map) - symbols", () => { - const snapshots = [] - const x = Symbol() - const y = Symbol() - const z = Symbol("z") - const a = Symbol() - const todos = observable.map({ [a]: 3 }) - - reaction( - () => keys(todos), - keys => snapshots.push(keys) - ) - - set(todos, x, 3) - remove(todos, y) - set(todos, z, 4) - set(todos, x, 5) - remove(todos, z) - remove(todos, a) - - expect(snapshots).toEqual([[a, x], [a, x, z], [a, x], [x]]) -}) - -test("values(array)", () => { - const todos = observable.array() - const snapshots = [] - - reaction( - () => values(todos), - values => snapshots.push(values) - ) - - expect(has(todos, 0)).toBe(false) - expect(get(todos, 0)).toBe(undefined) - set(todos, 0, 2) - expect(has(todos, 0)).toBe(true) - expect(get(todos, 0)).toBe(2) - - set(todos, "1", 4) - set(todos, 3, 4) - set(todos, 1, 3) - remove(todos, 2) - remove(todos, "0") - - expect(snapshots).toEqual([ - [2], - [2, 4], - [2, 4, undefined, 4], - [2, 3, undefined, 4], - [2, 3, 4], - [3, 4] - ]) -}) - -test("entries(array)", () => { - const todos = observable.array() - const snapshots = [] - - reaction( - () => entries(todos), - entries => snapshots.push(entries) - ) - - expect(has(todos, 0)).toBe(false) - expect(get(todos, 0)).toBe(undefined) - set(todos, 0, 2) - expect(has(todos, 0)).toBe(true) - expect(get(todos, 0)).toBe(2) - - set(todos, "1", 4) - set(todos, 3, 4) - set(todos, 1, 3) - remove(todos, 2) - remove(todos, "0") - - expect(snapshots).toEqual([ - [[0, 2]], - [ - [0, 2], - [1, 4] - ], - [ - [0, 2], - [1, 4], - [2, undefined], - [3, 4] - ], - [ - [0, 2], - [1, 3], - [2, undefined], - [3, 4] - ], - [ - [0, 2], - [1, 3], - [2, 4] - ], - [ - [0, 3], - [1, 4] - ] - ]) -}) - -test("keys(array)", () => { - const todos = observable.array() - const snapshots = [] - - reaction( - () => keys(todos), - keys => snapshots.push(keys) - ) - - set(todos, 0, 2) - set(todos, "1", 4) - set(todos, 3, 4) - set(todos, 1, 3) - remove(todos, 2) - remove(todos, "0") - - expect(snapshots).toEqual([[0], [0, 1], [0, 1, 2, 3], [0, 1, 2, 3], [0, 1, 2], [0, 1]]) -}) - -test("dynamically adding properties should preserve the original modifiers of an object", () => { - const todos = observable.object( - { - a: { title: "get coffee" } - }, - {}, - { deep: false } - ) - expect(mobx.isObservable(todos.a)).toBe(false) - set(todos, { b: { title: "get tea" } }) - expect(mobx.isObservable(todos.b)).toBe(false) -}) - -test("has and get are reactive", async () => { - const todos = observable({}) - - const p1 = when(() => has(todos, "x")) - const p2 = when(() => get(todos, "y") === 3) - - setTimeout(() => { - set(todos, { x: false, y: 3 }) - }, 100) - - await p1 - await p2 -}) - -test("computed props are considered part of collections", () => { - const x = observable({ - get y() { - return 3 - } - }) - expect(mobx.isComputedProp(x, "y")).toBe(true) - expect(x.y).toBe(3) - expect(has(x, "y")).toBe(true) - expect(get(x, "y")).toBe(3) - expect(keys(x)).toEqual([]) - expect(values(x)).toEqual([]) - expect(entries(x)).toEqual([]) -}) - -test("#1739 - delete and undelete should work", () => { - const x = observable({}) - - const events = [] - autorun(() => { - events.push(has(x, "a")) - }) - - set(x, "a", 1) - set(x, "a", 2) - remove(x, "a") - set(x, "a", 2) - remove(x, "a") - set(x, "a", 3) - expect(events).toEqual([false, true, false, true, false, true]) -}) - -test("keys(set)", () => { - const todos = observable.set([1]) - const snapshots = [] - - reaction( - () => keys(todos), - keys => snapshots.push(keys) - ) - - set(todos, 2) - remove(todos, 2) - set(todos, 3) - set(todos, 4) - remove(todos, 3) - - expect(snapshots).toEqual([[1, 2], [1], [1, 3], [1, 3, 4], [1, 4]]) -}) - -test("defineProperty - configurable: false", () => { - const obj = mobx.observable({}) - const desc = { - enumerable: true, - configurable: false, - writable: true, - value: 0 - } - mobx.defineProperty(obj, "foo", desc) - expect(Object.getOwnPropertyDescriptor(obj, "foo")).toEqual(desc) - expect(mobx.isObservableProp(obj, "foo")).toBe(false) - obj.foo++ - expect(obj.foo).toBe(1) - expect(() => mobx.extendObservable(obj, { foo: 0 })).toThrow(TypeError) - expect(() => mobx.makeObservable(obj, { foo: mobx.observable })).toThrow(TypeError) - expect(() => mobx.defineProperty(obj, "foo", { configurable: false })).toThrow(TypeError) -}) - -test("defineProperty - writable: false", () => { - const obj = mobx.observable({}) - const desc = { - enumerable: true, - configurable: true, - writable: false, - value: 0 - } - mobx.defineProperty(obj, "foo", desc) - expect(Object.getOwnPropertyDescriptor(obj, "foo")).toEqual(desc) - expect(mobx.isObservableProp(obj, "foo")).toBe(false) - expect(() => obj.foo++).toThrow(TypeError) - mobx.extendObservable(obj, { foo: 0 }) - expect(mobx.isObservableProp(obj, "foo")).toBe(true) - obj.foo++ - expect(obj.foo).toBe(1) -}) - -test("defineProperty - redefine observable", () => { - const obj = mobx.observable({ foo: 0 }) - expect(mobx.isObservableProp(obj, "foo")).toBe(true) - const desc = { - enumerable: true, - configurable: true, - writable: false, - value: 0 - } - mobx.defineProperty(obj, "foo", desc) - expect(Object.getOwnPropertyDescriptor(obj, "foo")).toEqual(desc) - expect(mobx.isObservableProp(obj, "foo")).toBe(false) -}) - -test("defineProperty notifies keys observers", () => { - const obj = mobx.observable({}) - let reactionCount = 0 - reaction( - () => mobx.keys(obj), - () => reactionCount++ - ) - - const desc = { - enumerable: true, - configurable: true, - writable: true, - value: 0 - } - mobx.defineProperty(obj, "foo", desc) - expect(Object.getOwnPropertyDescriptor(obj, "foo")).toEqual(desc) - expect(mobx.isObservableProp(obj, "foo")).toBe(false) - expect(reactionCount).toBe(1) - mobx.remove(obj, "foo") - expect(obj.hasOwnProperty("foo")).toBe(false) - expect(reactionCount).toBe(2) -}) diff --git a/packages/mobx/__tests__/base/proxies.js b/packages/mobx/__tests__/base/proxies.js index f548e567f..a71a91ddb 100644 --- a/packages/mobx/__tests__/base/proxies.js +++ b/packages/mobx/__tests__/base/proxies.js @@ -9,7 +9,6 @@ import { actionBound, reaction, extendObservable, - keys, makeObservable } from "../../src/mobx" @@ -129,12 +128,10 @@ test("correct keys are reported", () => { ]) expect(Object.getOwnPropertyNames(x)).toEqual(["x", "y", "z", "a", "b"]) - expect(keys(x)).toEqual(["x", "z", "a"]) delete x.x expect(Object.keys(x)).toEqual(["z", "a"]) expect(Object.getOwnPropertyNames(x)).toEqual(["y", "z", "a", "b"]) - expect(keys(x)).toEqual(["z", "a"]) }) test("in operator", () => { diff --git a/packages/mobx/__tests__/base/set.js b/packages/mobx/__tests__/base/set.js index 36f638ca8..b14c4cde2 100644 --- a/packages/mobx/__tests__/base/set.js +++ b/packages/mobx/__tests__/base/set.js @@ -16,9 +16,9 @@ test("set crud", function () { expect(s.has("2")).toBe(true) expect(s.size).toBe(2) - expect(mobx.keys(s)).toEqual([1, "2"]) - expect(mobx.values(s)).toEqual([1, "2"]) - expect(mobx.entries(s)).toEqual([ + expect([...s.keys()]).toEqual([1, "2"]) + expect([...s.values()]).toEqual([1, "2"]) + expect([...s.entries()]).toEqual([ [1, 1], ["2", "2"] ]) @@ -29,8 +29,8 @@ test("set crud", function () { s.replace(new Set([3])) - expect(mobx.keys(s)).toEqual([3]) - expect(mobx.values(s)).toEqual([3]) + expect([...s.keys()]).toEqual([3]) + expect([...s.values()]).toEqual([3]) expect(s.size).toBe(1) expect(s.has(1)).toBe(false) expect(s.has("2")).toBe(false) @@ -38,8 +38,8 @@ test("set crud", function () { s.replace(set([4])) - expect(mobx.keys(s)).toEqual([4]) - expect(mobx.values(s)).toEqual([4]) + expect([...s.keys()]).toEqual([4]) + expect([...s.values()]).toEqual([4]) expect(s.size).toBe(1) expect(s.has(1)).toBe(false) expect(s.has("2")).toBe(false) @@ -51,8 +51,8 @@ test("set crud", function () { }).toThrow(/Cannot initialize set from/) s.clear() - expect(mobx.keys(s)).toEqual([]) - expect(mobx.values(s)).toEqual([]) + expect([...s.keys()]).toEqual([]) + expect([...s.values()]).toEqual([]) expect(s.size).toBe(0) expect(s.has(1)).toBe(false) expect(s.has("2")).toBe(false) @@ -83,7 +83,7 @@ test("observe value", function () { s.replace(["y"]) expect(hasX).toBe(false) expect(hasY).toBe(true) - expect(mobx.values(s)).toEqual(["y"]) + expect([...s.values()]).toEqual(["y"]) }) test("observe collections", function () { @@ -91,7 +91,7 @@ test("observe collections", function () { let keys, values, entries autorun(function () { - keys = mobx.keys(x) + keys = [...x.keys()] }) autorun(function () { values = Array.from(x.values()) diff --git a/packages/mobx/__tests__/base/typescript-tests.ts b/packages/mobx/__tests__/base/typescript-tests.ts index 740631278..b1fbba069 100644 --- a/packages/mobx/__tests__/base/typescript-tests.ts +++ b/packages/mobx/__tests__/base/typescript-tests.ts @@ -1554,7 +1554,7 @@ test("multiple inheritance should work", () => { } } - expect(mobx.keys(new B())).toEqual(["x", "y"]) + expect(Object.keys(new B())).toEqual(["x", "y"]) }) // 19.12.2020 @urugator: diff --git a/packages/mobx/src/api/object-api.ts b/packages/mobx/src/api/object-api.ts index 86cb33832..7b0e09fad 100644 --- a/packages/mobx/src/api/object-api.ts +++ b/packages/mobx/src/api/object-api.ts @@ -1,177 +1,4 @@ -import { - $mobx, - IIsObservableObject, - IObservableArray, - ObservableMap, - ObservableSet, - ObservableObjectAdministration, - endBatch, - isObservableArray, - isObservableMap, - isObservableSet, - isObservableObject, - startBatch, - die -} from "../internal" - -export function keys(map: ObservableMap): ReadonlyArray -export function keys(ar: IObservableArray): ReadonlyArray -export function keys(set: ObservableSet): ReadonlyArray -export function keys(obj: T): ReadonlyArray -export function keys(obj: any): any { - if (isObservableObject(obj)) { - return ( - (obj as any as IIsObservableObject)[$mobx] as ObservableObjectAdministration - ).keys_() - } - if (isObservableMap(obj) || isObservableSet(obj)) { - return Array.from(obj.keys()) - } - if (isObservableArray(obj)) { - return obj.map((_, index) => index) - } - die(5) -} - -export function values(map: ObservableMap): ReadonlyArray -export function values(set: ObservableSet): ReadonlyArray -export function values(ar: IObservableArray): ReadonlyArray -export function values(obj: T): ReadonlyArray -export function values(obj: any): string[] { - if (isObservableObject(obj)) { - return keys(obj).map(key => obj[key]) - } - if (isObservableMap(obj)) { - return keys(obj).map(key => obj.get(key)) - } - if (isObservableSet(obj)) { - return Array.from(obj.values()) - } - if (isObservableArray(obj)) { - return obj.slice() - } - die(6) -} - -export function entries(map: ObservableMap): ReadonlyArray<[K, T]> -export function entries(set: ObservableSet): ReadonlyArray<[T, T]> -export function entries(ar: IObservableArray): ReadonlyArray<[number, T]> -export function entries( - obj: T -): ReadonlyArray<[string, T extends object ? T[keyof T] : any]> -export function entries(obj: any): any { - if (isObservableObject(obj)) { - return keys(obj).map(key => [key, obj[key]]) - } - if (isObservableMap(obj)) { - return keys(obj).map(key => [key, obj.get(key)]) - } - if (isObservableSet(obj)) { - return Array.from(obj.entries()) - } - if (isObservableArray(obj)) { - return obj.map((key, index) => [index, key]) - } - die(7) -} - -export function set(obj: ObservableMap, values: { [key: string]: V }) -export function set(obj: ObservableMap, key: K, value: V) -export function set(obj: ObservableSet, value: T) -export function set(obj: IObservableArray, index: number, value: T) -export function set(obj: T, values: { [key: string]: any }) -export function set(obj: T, key: PropertyKey, value: any) -export function set(obj: any, key: any, value?: any): void { - if (arguments.length === 2 && !isObservableSet(obj)) { - startBatch() - const values = key - try { - for (let key in values) { - set(obj, key, values[key]) - } - } finally { - endBatch() - } - return - } - if (isObservableObject(obj)) { - ;(obj as any as IIsObservableObject)[$mobx].set_(key, value) - } else if (isObservableMap(obj)) { - obj.set(key, value) - } else if (isObservableSet(obj)) { - obj.add(key) - } else if (isObservableArray(obj)) { - if (typeof key !== "number") { - key = parseInt(key, 10) - } - if (key < 0) { - die(42, key) - } - startBatch() - if (key >= obj.length) { - obj.length = key + 1 - } - obj[key] = value - endBatch() - } else { - die(8) - } -} - -export function remove(obj: ObservableMap, key: K) -export function remove(obj: ObservableSet, key: T) -export function remove(obj: IObservableArray, index: number) -export function remove(obj: T, key: string) -export function remove(obj: any, key: any): void { - if (isObservableObject(obj)) { - ;(obj as any as IIsObservableObject)[$mobx].delete_(key) - } else if (isObservableMap(obj)) { - obj.delete(key) - } else if (isObservableSet(obj)) { - obj.delete(key) - } else if (isObservableArray(obj)) { - if (typeof key !== "number") { - key = parseInt(key, 10) - } - obj.splice(key, 1) - } else { - die(9) - } -} - -export function has(obj: ObservableMap, key: K): boolean -export function has(obj: ObservableSet, key: T): boolean -export function has(obj: IObservableArray, index: number): boolean -export function has(obj: T, key: string): boolean -export function has(obj: any, key: any): boolean { - if (isObservableObject(obj)) { - return (obj as any as IIsObservableObject)[$mobx].has_(key) - } else if (isObservableMap(obj)) { - return obj.has(key) - } else if (isObservableSet(obj)) { - return obj.has(key) - } else if (isObservableArray(obj)) { - return key >= 0 && key < obj.length - } - die(10) -} - -export function get(obj: ObservableMap, key: K): V | undefined -export function get(obj: IObservableArray, index: number): T | undefined -export function get(obj: T, key: string): any -export function get(obj: any, key: any): any { - if (!has(obj, key)) { - return undefined - } - if (isObservableObject(obj)) { - return (obj as any as IIsObservableObject)[$mobx].get_(key) - } else if (isObservableMap(obj)) { - return obj.get(key) - } else if (isObservableArray(obj)) { - return obj[key] - } - die(11) -} +import { $mobx, IIsObservableObject, isObservableObject, die } from "../internal" export function apiDefineProperty(obj: Object, key: PropertyKey, descriptor: PropertyDescriptor) { if (isObservableObject(obj)) { @@ -179,10 +6,3 @@ export function apiDefineProperty(obj: Object, key: PropertyKey, descriptor: Pro } die(39) } - -export function apiOwnKeys(obj: Object) { - if (isObservableObject(obj)) { - return (obj as any as IIsObservableObject)[$mobx].ownKeys_() - } - die(38) -} diff --git a/packages/mobx/src/api/tojs.ts b/packages/mobx/src/api/tojs.ts index 2cc927b36..1f076893f 100644 --- a/packages/mobx/src/api/tojs.ts +++ b/packages/mobx/src/api/tojs.ts @@ -1,4 +1,5 @@ import { + $mobx, isObservable, isObservableArray, isObservableValue, @@ -6,7 +7,6 @@ import { isObservableSet, isComputedValue, die, - apiOwnKeys, objectPrototype } from "../internal" @@ -54,7 +54,7 @@ function toJSHelper(source, __alreadySeen: Map) { } else { // must be observable object const res = cache(__alreadySeen, source, {}) - apiOwnKeys(source).forEach((key: any) => { + ;(source[$mobx] as any).ownKeys_().forEach((key: any) => { if (objectPrototype.propertyIsEnumerable.call(source, key)) { res[key] = toJSHelper(source[key], __alreadySeen) } diff --git a/packages/mobx/src/errors.ts b/packages/mobx/src/errors.ts index 6333483ce..41c4361d9 100644 --- a/packages/mobx/src/errors.ts +++ b/packages/mobx/src/errors.ts @@ -14,13 +14,6 @@ export const niceErrors = { return `Cannot decorate '${prop.toString()}': computed can only be used on getter properties.` }, */ - 5: "'keys()' can only be used on observable objects, arrays, sets and maps", - 6: "'values()' can only be used on observable objects, arrays, sets and maps", - 7: "'entries()' can only be used on observable objects, arrays and maps", - 8: "'set()' can only be used on observable objects, arrays and maps", - 9: "'remove()' can only be used on observable objects, arrays and maps", - 10: "'has()' can only be used on observable objects, arrays and maps", - 11: "'get()' can only be used on observable objects, arrays and maps", 12: `Invalid annotation`, 13: `Dynamic observable objects cannot be frozen. If you're passing observables to 3rd party component/function that calls Object.freeze, pass copy instead: toJS(observable)`, 15: `Observable arrays cannot be frozen. If you're passing observables to 3rd party component/function that calls Object.freeze, pass copy instead: toJS(observable)`, @@ -65,7 +58,6 @@ export const niceErrors = { 37(method) { return `[mobx] \`observableArray.${method}()\` mutates the array in-place, which is not allowed inside a derivation. Use \`array.slice().${method}()\` instead` }, - 38: "'ownKeys()' can only be used on observable objects", 39: "'defineProperty()' can only be used on observable objects", 40(length) { return "Out of range: " + length @@ -73,9 +65,6 @@ export const niceErrors = { 41(other) { return "Cannot initialize set from " + other }, - 42(key) { - return `Invalid index: '${key}'` - }, 43(annotationType, name, kind) { return ( `Cannot apply '${annotationType}' to '${name}' (kind: ${kind}):` + diff --git a/packages/mobx/src/mobx.ts b/packages/mobx/src/mobx.ts index 509ebaf1b..e8a4b37db 100644 --- a/packages/mobx/src/mobx.ts +++ b/packages/mobx/src/mobx.ts @@ -82,14 +82,6 @@ export { isAction, runInAction, IActionFactory, - keys, - values, - entries, - set, - remove, - has, - get, - apiOwnKeys as ownKeys, apiDefineProperty as defineProperty, configure, onBecomeObserved, diff --git a/website/i18n/en.json b/website/i18n/en.json index ad085de42..293c45c56 100644 --- a/website/i18n/en.json +++ b/website/i18n/en.json @@ -36,10 +36,6 @@ "best/what-does-mobx-react-to": { "title": "Understanding reactivity" }, - "collection-utilities": { - "title": "Collection utilities", - "sidebar_label": "Collection utilities {🚀}" - }, "computeds-with-args": { "title": "Computeds with arguments", "sidebar_label": "Computeds with arguments {🚀}" diff --git a/website/sidebars.json b/website/sidebars.json index 4c8623e72..ea310c6a9 100755 --- a/website/sidebars.json +++ b/website/sidebars.json @@ -26,8 +26,7 @@ "computeds-with-args", "mobx-utils", "custom-observables", - "lazy-observables", - "collection-utilities" + "lazy-observables" ], "Fine-tuning": [ "configuration", From c2d710b4e5c1319fe36b2417c8dcc81a279e9cda Mon Sep 17 00:00:00 2001 From: Michel Weststrate Date: Thu, 30 Jul 2026 23:01:22 +0200 Subject: [PATCH 08/10] refactor(mobx): remove getDebugName/getDependencyTree/getObserverTree introspection APIs --- .changeset/remove-legacy-apis.md | 17 ++ docs/analyzing-reactivity.md | 46 ---- docs/api.md | 21 -- docs/understanding-reactivity.md | 14 -- packages/mobx-react-lite/src/useObserver.ts | 4 +- packages/mobx/__tests__/base/api.js | 3 - packages/mobx/__tests__/base/extras.js | 213 ------------------ .../mobx/__tests__/base/make-observable.ts | 5 +- packages/mobx/src/api/extras.ts | 43 ---- packages/mobx/src/internal.ts | 1 - packages/mobx/src/mobx.ts | 5 - website/i18n/en.json | 4 - website/sidebars.json | 1 - 13 files changed, 20 insertions(+), 357 deletions(-) create mode 100644 .changeset/remove-legacy-apis.md delete mode 100644 docs/analyzing-reactivity.md delete mode 100644 packages/mobx/src/api/extras.ts diff --git a/.changeset/remove-legacy-apis.md b/.changeset/remove-legacy-apis.md new file mode 100644 index 000000000..ede3eb161 --- /dev/null +++ b/.changeset/remove-legacy-apis.md @@ -0,0 +1,17 @@ +--- +"mobx": major +"mobx-react-lite": patch +--- + +Removed a number of legacy APIs and their supporting internals. + +Removed from `mobx`: + +- The top-level object API: `get`, `set`, `has`, `remove`, `keys`, `values`, `entries`, `ownKeys`. Use the native/instance equivalents instead (e.g. `map.get()`, `[...map.keys()]`, `Object.keys(obj)`, `"key" in obj`, `delete obj.key`). +- The interception/observation APIs: `intercept`, `observe`, `_interceptReads`, and the `spy` API. Use `autorun`/`reaction` to react to changes. +- The introspection APIs: `getDebugName`, `getDependencyTree`, `getObserverTree`. +- The `dehancer` mechanism (used internally by the removed `_interceptReads`; it also backed some mobx-state-tree integrations, which are no longer supported). + +All associated change-event interfaces (`I*WillChange` / `I*DidChange`), the `IInterceptable`/`IInterceptor`/`IListenable` interfaces, and the related internal machinery have been removed as well. The MobX devtools global hook (which depended on `spy`) has been removed. `MOBX_GLOBALS_VERSION` was bumped accordingly. + +`mobx-react-lite` no longer wires the removed `getDependencyTree` into `React.useDebugValue`. diff --git a/docs/analyzing-reactivity.md b/docs/analyzing-reactivity.md deleted file mode 100644 index 272c22409..000000000 --- a/docs/analyzing-reactivity.md +++ /dev/null @@ -1,46 +0,0 @@ ---- -title: Analyzing reactivity -sidebar_label: Analyzing reactivity {🚀} -hide_title: true ---- - - - -# Analyzing reactivity {🚀} - -# Introspection APIs - -The following APIs might come in handy if you want to inspect the internal state of MobX while debugging, or want to build cool tools on top of MobX. -Also relevant are the various [`isObservable*` APIs](api.md#isobservable). - -### `getDebugName` - -Usage: - -- `getDebugName(thing, property?)` - -Returns a (generated) friendly debug name of an observable object, property, reaction etc. Used for example by the [MobX developer tools](https://github.com/mobxjs/mobx-devtools). - -### `getDependencyTree` - -Usage: - -- `getDependencyTree(thing, property?)`. - -Returns a tree structure with all observables the given reaction / computation currently depends upon. - -### `getObserverTree` - -Usage: - -- `getObserverTree(thing, property?)`. - -Returns a tree structure with all reactions / computations that are observing the given observable. - -### `getAtom` - -Usage: - -- `getAtom(thing, property?)`. - -Returns the backing _Atom_ of a given observable object, property, reaction etc. diff --git a/docs/api.md b/docs/api.md index 7b6ec1bcb..ed585b1d1 100644 --- a/docs/api.md +++ b/docs/api.md @@ -393,27 +393,6 @@ Is this a boxed computed value, created using `computed(() => expr)`? Is this a computed property? -### `getDebugName` - -{🚀} Usage: `getDebugName(reaction|array|Set|Map)` or `getDebugName(object|Map, propertyName)` -([further information](analyzing-reactivity.md#getdebugname)) - -Returns the (generated) friendly debug name for an observable or reaction. - -### `getDependencyTree` - -{🚀} Usage: `getDependencyTree(object, computedPropertyName)` -([further information](analyzing-reactivity.md#getdependencytree)) - -Returns a tree structure with all observables the given reaction / computation currently depends upon. - -### `getObserverTree` - -{🚀} Usage: `getObserverTree(array|Set|Map)` or `getObserverTree(object|Map, propertyName)` -([further information](analyzing-reactivity.md#getobservertree)) - -Returns a tree structure with all reactions / computations that are observing the given observable. - --- ## Extending MobX {🚀} diff --git a/docs/understanding-reactivity.md b/docs/understanding-reactivity.md index 2a2d3b5dc..58d8d718c 100644 --- a/docs/understanding-reactivity.md +++ b/docs/understanding-reactivity.md @@ -68,20 +68,6 @@ message.updateTitle("Bar") This will react as expected. The `.title` property was dereferenced by the autorun, and changed afterwards, so this change is detected. -You can inspect what MobX tracks by calling [`getDependencyTree`](api.md#getdependencytree) with the disposer returned by `autorun`: - -```javascript -import { getDependencyTree } from "mobx" - -const disposer = autorun(() => { - console.log(message.title) -}) - -// Outputs: -console.log(getDependencyTree(disposer)) -// { name: 'Autorun@2', dependencies: [ { name: 'Message@1.title' } ] } -``` - #### Incorrect: changing a non-observable reference ```javascript diff --git a/packages/mobx-react-lite/src/useObserver.ts b/packages/mobx-react-lite/src/useObserver.ts index 08da5b53f..d8b6c6aa4 100644 --- a/packages/mobx-react-lite/src/useObserver.ts +++ b/packages/mobx-react-lite/src/useObserver.ts @@ -1,4 +1,4 @@ -import { getDependencyTree, Reaction } from "mobx" +import { Reaction } from "mobx" import React from "react" import { isUsingStaticRendering } from "./staticRendering" import { observerFinalizationRegistry } from "./utils/observerFinalizationRegistry" @@ -87,8 +87,6 @@ export function useObserver(render: () => T, baseComponentName: string = "obs observerFinalizationRegistry.register(admRef, adm, adm) } - React.useDebugValue(adm.reaction!, getDependencyTree) - React.useSyncExternalStore( // Both of these must be stable, otherwise it would keep resubscribing every render. adm.subscribe, diff --git a/packages/mobx/__tests__/base/api.js b/packages/mobx/__tests__/base/api.js index b9cdd6e91..c66966899 100644 --- a/packages/mobx/__tests__/base/api.js +++ b/packages/mobx/__tests__/base/api.js @@ -35,10 +35,7 @@ test("correct api should be exposed", function () { "isFlowCancellationError", "_getAdministration", "getAtom", - "getDebugName", - "getDependencyTree", "_getGlobalState", - "getObserverTree", "isAction", "isBoxedObservable", "isComputed", diff --git a/packages/mobx/__tests__/base/extras.js b/packages/mobx/__tests__/base/extras.js index dc453717b..c0b611a5d 100644 --- a/packages/mobx/__tests__/base/extras.js +++ b/packages/mobx/__tests__/base/extras.js @@ -3,83 +3,6 @@ const m = mobx const { $mobx } = mobx -test("treeD", function () { - m._resetGlobalState() - mobx._getGlobalState().mobxGuid = 0 - const a = m.observable.box(3) - const aName = "ObservableValue@1" - - const dtree = m.getDependencyTree - expect(dtree(a)).toEqual({ - name: aName - }) - - const b = m.computed(() => a.get() * a.get()) - const bName = "ComputedValue@2" - expect(dtree(b)).toEqual({ - name: bName - // no dependencies yet, since it isn't observed yet - }) - - const c = m.autorun(() => b.get()) - const cName = "Autorun@3" - expect(dtree(c[$mobx])).toEqual({ - name: cName, - dependencies: [ - { - name: bName, - dependencies: [ - { - name: aName - } - ] - } - ] - }) - - expect(aName !== bName).toBeTruthy() - expect(bName !== cName).toBeTruthy() - - expect(m.getObserverTree(a)).toEqual({ - name: aName, - observers: [ - { - name: bName, - observers: [ - { - name: cName - } - ] - } - ] - }) - - const x = mobx.observable.map({ temperature: 0 }) - const d = mobx.autorun(function () { - Array.from(x.keys()) - if (x.has("temperature")) x.get("temperature") - x.has("absent") - }) - - expect(m.getDependencyTree(d[$mobx])).toEqual({ - name: "Autorun@5", - dependencies: [ - { - name: "ObservableMap@4.keys()" - }, - { - name: "ObservableMap@4.temperature?" - }, - { - name: "ObservableMap@4.temperature" - }, - { - name: "ObservableMap@4.absent?" - } - ] - }) -}) - test("names", function () { m._resetGlobalState() mobx._getGlobalState().mobxGuid = 0 @@ -183,54 +106,6 @@ test("get atom", function () { f() }) -test("get debug name", function () { - mobx._resetGlobalState() - mobx._getGlobalState().mobxGuid = 0 // hmm dangerous reset? - - function Clazz() { - mobx.extendObservable(this, { - a: 17 - }) - } - - const a = mobx.observable.box(3) - const b = mobx.observable({ a: 3 }) - const c = mobx.observable.map({ a: 3 }) - const d = mobx.observable([1, 2]) - const e = mobx.computed(() => 3) - const f = mobx.autorun(() => c.has("b")) - const g = new Clazz() - - function name(thing, prop) { - return mobx.getDebugName(thing, prop) - } - - expect(name(a)).toBe("ObservableValue@1") - - expect(name(b, "a")).toBe("ObservableObject@2.a") - expect(() => name(b, "b")).toThrow( - /no observable property 'b' found on the observable object 'ObservableObject@2'/ - ) - - expect(name(c)).toBe("ObservableMap@3") // returns ke, "bla"ys - expect(name(c, "a")).toBe("ObservableMap@3.a") // returns ent, "bla"ry - expect(name(c, "b")).toBe("ObservableMap@3.b?") // returns has entry (see autoru, "bla"n) - expect(() => name(c, "c")).toThrow( - /the entry 'c' does not exist in the observable map 'ObservableMap@3'/ - ) - - expect(name(d)).toBe("ObservableArray@4") - expect(() => name(d, 0)).toThrow(/It is not possible to get index atoms from arrays/) - - expect(name(e)).toBe("ComputedValue@5") - expect(name(f)).toBe("Autorun@6") - - expect(name(g)).toBe("Clazz@7") - expect(name(g, "a")).toBe("Clazz@7.a") - - f() -}) - test("get administration", function () { mobx._resetGlobalState() mobx._getGlobalState().mobxGuid = 0 // hmm dangerous reset? @@ -843,91 +718,3 @@ test("compareShallow should work", () => { expect(sh(obs(new Map([[{}, 1]])), obs(new Map([[{}, 1]])))).toBe(false) expect(sh(obs(new Map([["a", {}]])), obs(new Map([["a", {}]])))).toBe(false) }) - -test("getDebugName(action)", () => { - expect(mobx.getDebugName(mobx.action(() => {}))).toBe("") - expect(mobx.getDebugName(mobx.action(function fn() {}))).toBe("fn") - expect(mobx.getDebugName(mobx.action("custom", function fn() {}))).toBe("custom") -}) - -test("Default debug names - development", () => { - expect(mobx.getDebugName(mobx.observable({ x() {} }, { x: mobx.action }).x)).toBe("x") - expect(/Atom@\d+/.test(mobx.getDebugName(mobx.createAtom()))).toBe(true) - expect(/ComputedValue@\d+/.test(mobx.getDebugName(mobx.computed(() => {})))).toBe(true) - expect(mobx.getDebugName(mobx.action(function fn() {}))).toBe("fn") - expect(/ObservableObject@\d+/.test(mobx.getDebugName(mobx.observable({})))).toBe(true) - expect(/ObservableObject@\d+.x/.test(mobx.getDebugName(mobx.observable({ x: "x" }), "x"))).toBe( - true - ) - expect( - /ObservableObject@\d+.x/.test(mobx.getDebugName(mobx.observable({ get x() {} }), "x")) - ).toBe(true) - expect(/ObservableArray@\d+/.test(mobx.getDebugName(mobx.observable([])))).toBe(true) - expect(/ObservableMap@\d+/.test(mobx.getDebugName(mobx.observable(new Map())))).toBe(true) - expect(/ObservableSet@\d+/.test(mobx.getDebugName(mobx.observable(new Set())))).toBe(true) - expect(/ObservableValue@\d+/.test(mobx.getDebugName(mobx.observable("x")))).toBe(true) - expect( - /Reaction@\d+/.test( - mobx.getDebugName( - mobx.reaction( - () => {}, - () => {} - ) - ) - ) - ).toBe(true) - expect(/Autorun@\d+/.test(mobx.getDebugName(mobx.autorun(() => {})))).toBe(true) -}) - -test("Default debug names - production", () => { - const mobx = require(`../../dist/mobx.cjs.production.min.js`) - - expect(mobx.getDebugName(mobx.observable({ x() {} }, { x: mobx.action }).x)).toBe("x") // perhaps should be ""?? - expect(mobx.getDebugName(mobx.createAtom())).toBe("Atom") - expect(mobx.getDebugName(mobx.computed(() => {}))).toBe("ComputedValue") - expect(mobx.getDebugName(mobx.action(function fn() {}))).toBe("fn") - expect(mobx.getDebugName(mobx.observable({}))).toBe("ObservableObject") - expect(mobx.getDebugName(mobx.observable({ x: "x" }), "x")).toBe("ObservableObject.key") - expect(mobx.getDebugName(mobx.observable({ get x() {} }), "x")).toBe("ObservableObject.key") - expect(mobx.getDebugName(mobx.observable([]))).toBe("ObservableArray") - expect(mobx.getDebugName(mobx.observable(new Map()))).toBe("ObservableMap") - expect(mobx.getDebugName(mobx.observable(new Set()))).toBe("ObservableSet") - expect(mobx.getDebugName(mobx.observable("x"))).toBe("ObservableValue") - expect( - mobx.getDebugName( - mobx.reaction( - () => {}, - () => {} - ) - ) - ).toBe("Reaction") - expect(mobx.getDebugName(mobx.autorun(() => {}))).toBe("Autorun") -}) - -test("User provided debug names are always respected", () => { - const mobxDevelopment = mobx - const mobxProduction = require(`../../dist/mobx.cjs.production.min.js`) - - const name = "CustomName" - - ;[mobxDevelopment, mobxProduction].forEach(mobx => { - expect(mobx.getDebugName(mobx.action(name, function fn() {}))).toBe(name) - expect(mobx.getDebugName(mobx.createAtom(name))).toBe(name) - expect(mobx.getDebugName(mobx.computed(() => {}, { name }))).toBe(name) - expect(mobx.getDebugName(mobx.observable({}, {}, { name }))).toBe(name) - expect(mobx.getDebugName(mobx.observable([], { name }))).toBe(name) - expect(mobx.getDebugName(mobx.observable(new Map(), { name }))).toBe(name) - expect(mobx.getDebugName(mobx.observable(new Set(), { name }))).toBe(name) - expect(mobx.getDebugName(mobx.observable("x", { name }))).toBe(name) - expect( - mobx.getDebugName( - mobx.reaction( - () => {}, - () => {}, - { name } - ) - ) - ).toBe(name) - expect(mobx.getDebugName(mobx.autorun(() => {}, { name }))).toBe(name) - }) -}) diff --git a/packages/mobx/__tests__/base/make-observable.ts b/packages/mobx/__tests__/base/make-observable.ts index cd861e832..d1da4335a 100644 --- a/packages/mobx/__tests__/base/make-observable.ts +++ b/packages/mobx/__tests__/base/make-observable.ts @@ -16,7 +16,6 @@ import { makeAutoObservable, autorun, extendObservable, - getDebugName, _getAdministration, configure, flow, @@ -585,8 +584,8 @@ test("makeObservable respects options.name #2614'", () => { const instance = new Clazz() const plain = makeObservable({ timer: 0 }, { timer: observable }, { name }) - expect(getDebugName(instance)).toBe(name) - expect(getDebugName(plain)).toBe(name) + expect(_getAdministration(instance).name_).toBe(name) + expect(_getAdministration(plain).name_).toBe(name) }) // "makeObservable + action + arrow function + subclass override #2614" diff --git a/packages/mobx/src/api/extras.ts b/packages/mobx/src/api/extras.ts deleted file mode 100644 index 0b76c1bfd..000000000 --- a/packages/mobx/src/api/extras.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { IDepTreeNode, getAtom, getObservers, hasObservers } from "../internal" - -export interface IDependencyTree { - name: string - dependencies?: IDependencyTree[] -} - -export interface IObserverTree { - name: string - observers?: IObserverTree[] -} - -export function getDependencyTree(thing: any, property?: string): IDependencyTree { - return nodeToDependencyTree(getAtom(thing, property)) -} - -function nodeToDependencyTree(node: IDepTreeNode): IDependencyTree { - const result: IDependencyTree = { - name: node.name_ - } - if (node.observing_ && node.observing_.length > 0) { - result.dependencies = unique(node.observing_).map(nodeToDependencyTree) - } - return result -} - -export function getObserverTree(thing: any, property?: string): IObserverTree { - return nodeToObserverTree(getAtom(thing, property)) -} - -function nodeToObserverTree(node: IDepTreeNode): IObserverTree { - const result: IObserverTree = { - name: node.name_ - } - if (hasObservers(node as any)) { - result.observers = Array.from(getObservers(node as any), nodeToObserverTree) - } - return result -} - -function unique(list: T[]): T[] { - return Array.from(new Set(list)) -} diff --git a/packages/mobx/src/internal.ts b/packages/mobx/src/internal.ts index c3f7065a3..d1d1d537c 100644 --- a/packages/mobx/src/internal.ts +++ b/packages/mobx/src/internal.ts @@ -32,7 +32,6 @@ export * from "./api/autorun" export * from "./api/become-observed" export * from "./api/configure" export * from "./api/extendobservable" -export * from "./api/extras" export * from "./api/flow" export * from "./api/iscomputed" export * from "./api/isobservable" diff --git a/packages/mobx/src/mobx.ts b/packages/mobx/src/mobx.ts index e8a4b37db..546578583 100644 --- a/packages/mobx/src/mobx.ts +++ b/packages/mobx/src/mobx.ts @@ -94,13 +94,8 @@ export { FlowCancellationError, isFlowCancellationError, toJS, - IObserverTree, - IDependencyTree, - getDependencyTree, - getObserverTree, resetGlobalState as _resetGlobalState, getGlobalState as _getGlobalState, - getDebugName, getAtom, getAdministration as _getAdministration, allowStateChanges as _allowStateChanges, diff --git a/website/i18n/en.json b/website/i18n/en.json index 293c45c56..d1db195dd 100644 --- a/website/i18n/en.json +++ b/website/i18n/en.json @@ -13,10 +13,6 @@ "title": "Updating state using actions", "sidebar_label": "Actions" }, - "analyzing-reactivity": { - "title": "Analyzing reactivity", - "sidebar_label": "Analyzing reactivity {🚀}" - }, "api": { "title": "MobX API Reference", "sidebar_label": "API" diff --git a/website/sidebars.json b/website/sidebars.json index ea310c6a9..8151c4d34 100755 --- a/website/sidebars.json +++ b/website/sidebars.json @@ -21,7 +21,6 @@ "defining-data-stores", "understanding-reactivity", "subclassing", - "analyzing-reactivity", "errors", "computeds-with-args", "mobx-utils", From 6cdef7358a1e2edad9667bea052a9d132621788b Mon Sep 17 00:00:00 2001 From: Michel Weststrate Date: Thu, 30 Jul 2026 23:10:47 +0200 Subject: [PATCH 09/10] docs: remove links to deleted intercept/observe/collection/analyzing pages --- docs/api.md | 1 - docs/best/debugging-mobx.md | 10 ---------- docs/computeds.md | 2 +- docs/migrating-from-6-to-7.md | 2 +- docs/react-integration.md | 2 +- docs/reactions.md | 2 +- docs/refguide/object-api.md | 10 ---------- docs/refguide/observe.md | 10 ---------- website/i18n/en.json | 9 --------- 9 files changed, 4 insertions(+), 44 deletions(-) delete mode 100644 docs/best/debugging-mobx.md delete mode 100644 docs/refguide/object-api.md delete mode 100644 docs/refguide/observe.md diff --git a/docs/api.md b/docs/api.md index ed585b1d1..f5e7d5ad9 100644 --- a/docs/api.md +++ b/docs/api.md @@ -412,7 +412,6 @@ Creates your own observable data structure and hooks it up to MobX. Used interna ### `getAtom` {🚀} Usage: `getAtom(thing, property?)` -([further information](analyzing-reactivity.md#getatom)) Returns the backing atom. diff --git a/docs/best/debugging-mobx.md b/docs/best/debugging-mobx.md deleted file mode 100644 index 20bcd51d4..000000000 --- a/docs/best/debugging-mobx.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -title: Analyzing reactivity -hide_title: true ---- - - - -# This document has been updated and moved - -[Please click on this link to open the updated version.](../analyzing-reactivity.md) diff --git a/docs/computeds.md b/docs/computeds.md index d0f2eb00f..0f608789c 100644 --- a/docs/computeds.md +++ b/docs/computeds.md @@ -210,7 +210,7 @@ This form of `computed` is not used very often, but in some cases where you need ### `name` -This string is used as a debug name in the [Spy event listeners](analyzing-reactivity.md#spy) and [MobX developer tools](https://github.com/mobxjs/mobx-devtools). +This string is used as a debug name in error messages and third-party developer tooling such as [mobx-log](https://github.com/kubk/mobx-log). ### `equals` diff --git a/docs/migrating-from-6-to-7.md b/docs/migrating-from-6-to-7.md index 02921f6c7..36845a1dc 100644 --- a/docs/migrating-from-6-to-7.md +++ b/docs/migrating-from-6-to-7.md @@ -209,7 +209,7 @@ Also remove `{ proxy: false }` from `observable`, `observable.object` and `obser ## Removed `trace` -The `trace` API has been removed. For debugging reactivity, use [`getDependencyTree`](api.md#getdependencytree), [`getObserverTree`](api.md#getobservertree), [`spy`](analyzing-reactivity.md#spy), the MobX developer tools, or packages such as `mobx-log`. +The `trace` API has been removed. For debugging reactivity, use packages such as `mobx-log`. ```javascript import { autorun, getDependencyTree } from "mobx" diff --git a/docs/react-integration.md b/docs/react-integration.md index 8ab2197a0..1873fa220 100644 --- a/docs/react-integration.md +++ b/docs/react-integration.md @@ -537,4 +537,4 @@ Help! My component isn't re-rendering... 1. Make sure you grok how tracking works in general. Check out the [Understanding reactivity](understanding-reactivity.md) section. 1. Read the common pitfalls as described above. 1. [Configure](configuration.md#linting-options) MobX to warn you of unsound usage of mechanisms and check the console logs. -1. Use [spy](analyzing-reactivity.md#spy), [`getDependencyTree`](api.md#getdependencytree), or the [mobx-log](https://github.com/kubk/mobx-log) package to inspect what MobX is doing. +1. Use the [mobx-log](https://github.com/kubk/mobx-log) package to inspect what MobX is doing. diff --git a/docs/reactions.md b/docs/reactions.md index c5e2f53a8..fcfb5bcd3 100644 --- a/docs/reactions.md +++ b/docs/reactions.md @@ -402,7 +402,7 @@ The behavior of `autorun`, `reaction` and `when` can be further fine-tuned by pa ### `name` -This string is used as a debug name for this reaction in the [Spy event listeners](analyzing-reactivity.md#spy) and [MobX developer tools](https://github.com/mobxjs/mobx-devtools). +This string is used as a debug name for this reaction in error messages and third-party developer tooling such as [mobx-log](https://github.com/kubk/mobx-log). ### `fireImmediately` _(reaction)_ diff --git a/docs/refguide/object-api.md b/docs/refguide/object-api.md deleted file mode 100644 index 3ccb53346..000000000 --- a/docs/refguide/object-api.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -title: Collection utilities -hide_title: true ---- - - - -# This document has been updated and moved - -[Please click on this link to open the updated version.](../collection-utilities.md) diff --git a/docs/refguide/observe.md b/docs/refguide/observe.md deleted file mode 100644 index 2ca4c5349..000000000 --- a/docs/refguide/observe.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -title: Intercept & Observe -hide_title: true ---- - - - -# This document has been updated and moved - -[Please click on this link to open the updated version.](../intercept-and-observe.md) diff --git a/website/i18n/en.json b/website/i18n/en.json index d1db195dd..8c61271a8 100644 --- a/website/i18n/en.json +++ b/website/i18n/en.json @@ -20,9 +20,6 @@ "backers-sponsors": { "title": "MobX Backers and Sponsors" }, - "best/debugging-mobx": { - "title": "Analyzing reactivity" - }, "best/decorators": { "title": "Enabling decorators" }, @@ -148,18 +145,12 @@ "refguide/modifiers": { "title": "Observable modifiers" }, - "refguide/object-api": { - "title": "Collection utilities" - }, "refguide/object": { "title": "Observable Objects" }, "refguide/observable": { "title": "Creating observable state" }, - "refguide/observe": { - "title": "Intercept & Observe" - }, "refguide/on-become-observed": { "title": "Creating lazy observables" }, From 8ede27f83c984165b488635c6fdc8d6eeef1ec87 Mon Sep 17 00:00:00 2001 From: Michel Weststrate Date: Thu, 30 Jul 2026 23:32:30 +0200 Subject: [PATCH 10/10] test(mobx-react,mobx-react-lite): replace removed getObserverTree with getAtom-based helper --- packages/mobx-react-lite/__tests__/observer.test.tsx | 6 +++++- packages/mobx-react/__tests__/observer.test.tsx | 10 +++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/packages/mobx-react-lite/__tests__/observer.test.tsx b/packages/mobx-react-lite/__tests__/observer.test.tsx index f522c2e73..a8cb768f4 100644 --- a/packages/mobx-react-lite/__tests__/observer.test.tsx +++ b/packages/mobx-react-lite/__tests__/observer.test.tsx @@ -6,7 +6,11 @@ import React from "react" import { observer, enableStaticRendering } from "../src" import { useObserver } from "../src/useObserver" -const getDNode = (obj: any, prop?: string) => mobx.getObserverTree(obj, prop) +const getDNode = (obj: any, prop?: string) => { + const atom = mobx.getAtom(obj, prop) as any + const observers = atom.observers_ + return { observers: observers && observers.size > 0 ? Array.from(observers) : undefined } +} let consoleWarnMock: jest.SpyInstance | undefined diff --git a/packages/mobx-react/__tests__/observer.test.tsx b/packages/mobx-react/__tests__/observer.test.tsx index 1a3dc4f25..1afee8549 100644 --- a/packages/mobx-react/__tests__/observer.test.tsx +++ b/packages/mobx-react/__tests__/observer.test.tsx @@ -2,7 +2,7 @@ import React, { StrictMode, Suspense } from "react" import { observer, Observer, enableStaticRendering } from "../src" import { render, act, waitFor } from "@testing-library/react" import { - getObserverTree, + getAtom, _getGlobalState, action, computed, @@ -16,6 +16,14 @@ import { } from "mobx" import { withConsole } from "./utils/withConsole" import { shallowEqual } from "../src/utils/utils" + +// Local helper mirroring the (removed) `getObserverTree` shape, so we can assert +// how many observers are attached to a given observable/property. +function getObserverTree(obj: any, prop?: string): { observers?: any[] } { + const atom = getAtom(obj, prop) as any + const observers = atom.observers_ + return { observers: observers && observers.size > 0 ? Array.from(observers) : undefined } +} /** * some test suite is too tedious */