From 4dd2ef3b84f25c7d6ecad548ffe7d875a9c2b94f Mon Sep 17 00:00:00 2001 From: Xidorn Quan Date: Wed, 16 Jun 2021 08:47:42 +1000 Subject: [PATCH 1/2] Add computed.shallow annotation --- .changeset/smooth-glasses-search.md | 5 ++++ docs/observable-state.md | 1 + .../v5/base/typescript-decorators.ts | 28 ++++++++++++++++++- packages/mobx/src/api/computed.ts | 6 ++++ 4 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 .changeset/smooth-glasses-search.md diff --git a/.changeset/smooth-glasses-search.md b/.changeset/smooth-glasses-search.md new file mode 100644 index 0000000000..01ca749ae8 --- /dev/null +++ b/.changeset/smooth-glasses-search.md @@ -0,0 +1,5 @@ +--- +"mobx": minor +--- + +Add computed.shallow annotation diff --git a/docs/observable-state.md b/docs/observable-state.md index a9ae52c40c..de0c5b65ad 100644 --- a/docs/observable-state.md +++ b/docs/observable-state.md @@ -228,6 +228,7 @@ Note that it is possible to pass `{ proxy: false }` as an option to `observable` | `action` | Mark a method as an action that will modify the state. Check out [actions](actions.md) for more details. Non-writable. | | `action.bound` | Like action, but will also bind the action to the instance so that `this` will always be set. Non-writable. | | `computed` | Can be used on a [getter](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get) to declare it as a derived value that can be cached. Check out [computeds](computeds.md) for more details. | +| `computed.shallow` | Like `computed`, but for collections. After recomputing, if the contents of the collection is equal to the previous result, no observers will be notified. | | `computed.struct` | Like `computed`, except that if after recomputing the result is structurally equal to the previous result, no observers will be notified. | | `true` | Infer the best annotation. Check out [makeAutoObservable](#makeautoobservable) for more details. | | `false` | Explicitly do not annotate this property. | diff --git a/packages/mobx/__tests__/v5/base/typescript-decorators.ts b/packages/mobx/__tests__/v5/base/typescript-decorators.ts index f49362f93d..e54ffdb8ff 100644 --- a/packages/mobx/__tests__/v5/base/typescript-decorators.ts +++ b/packages/mobx/__tests__/v5/base/typescript-decorators.ts @@ -207,7 +207,33 @@ test("computed setter should succeed", () => { t.equal(b.propX, 8) }) -test("typescript: parameterized computed decorator", () => { +test("@computed.shallow (TS)", () => { + class TestClass { + @observable.struct x = { a: 3 } + @observable.struct y = { b: 4 } + @computed.shallow + get array() { + return [this.x, this.y] + } + constructor() { + makeObservable(this) + } + } + + const t1 = new TestClass() + const changes: string[] = [] + const d = autorun(() => changes.push(JSON.stringify(t1.array))) + + t1.x = { a: 5 } // change + t.equal(changes.length, 2) + t1.x.a = 6 // no change + t.equal(changes.length, 2) + d() + + t.deepEqual(changes, ['[{"a":3},{"b":4}]', '[{"a":5},{"b":4}]']) +}) + +test("@computed.struct (TS)", () => { class TestClass { @observable x = 3 @observable y = 3 diff --git a/packages/mobx/src/api/computed.ts b/packages/mobx/src/api/computed.ts index e6ee0bf812..1fdb65db7c 100644 --- a/packages/mobx/src/api/computed.ts +++ b/packages/mobx/src/api/computed.ts @@ -14,6 +14,7 @@ import { } from "../internal" export const COMPUTED = "computed" +export const COMPUTED_SHALLOW = "computed.shallow" export const COMPUTED_STRUCT = "computed.struct" export interface IComputedFactory extends Annotation, PropertyDecorator { @@ -22,10 +23,14 @@ export interface IComputedFactory extends Annotation, PropertyDecorator { // computed(fn, opts) (func: () => T, options?: IComputedValueOptions): IComputedValue + shallow: Annotation & PropertyDecorator struct: Annotation & PropertyDecorator } const computedAnnotation = createComputedAnnotation(COMPUTED) +const computedShallowAnnotation = createComputedAnnotation(COMPUTED_SHALLOW, { + equals: comparer.shallow +}) const computedStructAnnotation = createComputedAnnotation(COMPUTED_STRUCT, { equals: comparer.structural }) @@ -61,4 +66,5 @@ export const computed: IComputedFactory = function computed(arg1, arg2) { Object.assign(computed, computedAnnotation) +computed.shallow = createDecoratorAnnotation(computedShallowAnnotation) computed.struct = createDecoratorAnnotation(computedStructAnnotation) From ba43d3f28a49c3f6391c139b19b9c94c1d36706a Mon Sep 17 00:00:00 2001 From: Xidorn Quan Date: Fri, 31 May 2024 14:46:44 +1000 Subject: [PATCH 2/2] add more documentation --- docs/computeds.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/computeds.md b/docs/computeds.md index 8dcbb7b945..d38c57ea67 100644 --- a/docs/computeds.md +++ b/docs/computeds.md @@ -151,6 +151,14 @@ class Dimension { +
{🚀} **Tip:** `computed.shallow` for comparing output shallowly + +If the output of a computed value that is a collection with all items identical to the collection from previous computation doesn't need to notify observers, `computed.shallow` can be used. It will do size check and reference equality check for each item, rather than a reference equality check of the collection, before notifying observers. + +In practice, `computed.shalow` is useful only in limited situations. Only use it if changes in the underlying observables can still lead to the same contents in the collection, for example, if we were filtering a list of rectangles by whether they are in a given area, the rectangles included might stay the same even though the area changes. + +
+
{🚀} **Tip:** `computed.struct` for comparing output structurally If the output of a computed value that is structurally equivalent to the previous computation doesn't need to notify observers, `computed.struct` can be used. It will make a structural comparison first, rather than a reference equality check, before notifying observers. For example: