From 0c45009d95074792df252830044a67dfd9d4e567 Mon Sep 17 00:00:00 2001 From: geekact Date: Mon, 16 Oct 2023 00:47:45 +0800 Subject: [PATCH] fix(computed): re-calculate when object path changed --- src/reactive/ObjectDeps.ts | 21 +++++++++------------ test/computed.test.ts | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 12 deletions(-) diff --git a/src/reactive/ObjectDeps.ts b/src/reactive/ObjectDeps.ts index 1bce8ac..ddadabd 100644 --- a/src/reactive/ObjectDeps.ts +++ b/src/reactive/ObjectDeps.ts @@ -23,15 +23,11 @@ export class ObjectDeps implements Deps { isDirty(): boolean { const rootState = this.getState(); - if (this.root === rootState) return false; - - if (this.snapshot === this.getSnapshot(rootState)) { - this.root = rootState; - return false; - } - - return true; + const { pathChanged, snapshot: nextSnapshot } = this.getSnapshot(rootState); + if (pathChanged || this.snapshot !== nextSnapshot) return true; + this.root = rootState; + return false; } get id(): string { @@ -51,16 +47,17 @@ export class ObjectDeps implements Deps { return this.store.getState()[this.model]; } - protected getSnapshot(state: any) { + protected getSnapshot(state: any): { pathChanged: boolean; snapshot: any } { const deps = this.deps; let snapshot = state; - for (let i = 0; i < deps.length; ++i) { - if (!isObject>(snapshot)) break; + if (!isObject>(snapshot)) { + return { pathChanged: true, snapshot }; + } snapshot = snapshot[deps[i]!]; } - return snapshot; + return { pathChanged: false, snapshot }; } protected proxy(currentState: Record): any { diff --git a/test/computed.test.ts b/test/computed.test.ts index a9e265e..23627e7 100644 --- a/test/computed.test.ts +++ b/test/computed.test.ts @@ -379,3 +379,41 @@ test('array should always be deps', () => { model.myData(); expect(spy).toBeCalledTimes(4); }); + +test('re-calculate when path changed', () => { + const spy = vitest.fn(); + + const model = defineModel('re-calculate-path-changed', { + initialState: { + foo: { bar: undefined } as undefined | { bar: string | undefined }, + baz: '123', + }, + reducers: { + updateFoo(state, foo: undefined | { bar: string | undefined }) { + state.foo = foo; + }, + }, + computed: { + myData() { + const foo = this.state.foo; + spy(); + return foo ? foo.bar : this.state.baz; + }, + }, + }); + + expect(model.myData()).toBeUndefined(); + expect(spy).toBeCalledTimes(1); + model.updateFoo(undefined); + expect(model.myData()).toBe('123'); + expect(spy).toBeCalledTimes(2); + model.updateFoo({ bar: undefined }); + expect(model.myData()).toBeUndefined(); + expect(spy).toBeCalledTimes(3); + model.updateFoo({ bar: 'abc' }); + expect(model.myData()).toBe('abc'); + expect(spy).toBeCalledTimes(4); + model.updateFoo({ bar: undefined }); + expect(model.myData()).toBeUndefined(); + expect(spy).toBeCalledTimes(5); +});