diff --git a/packages/reactive/src/__tests__/autorun.spec.ts b/packages/reactive/src/__tests__/autorun.spec.ts index 2e74766b38a..b8c1630defc 100644 --- a/packages/reactive/src/__tests__/autorun.spec.ts +++ b/packages/reactive/src/__tests__/autorun.spec.ts @@ -24,6 +24,12 @@ test('autorun', () => { expect(handler).toBeCalledTimes(2) }) +test('autorun first argument is not a function', () => { + autorun({} as any) + autorun(1 as any) + autorun('1' as any) +}) + test('reaction', () => { const obs = observable({ aa: { @@ -741,3 +747,51 @@ test('reaction recollect dependencies', () => { expect(fn2).toBeCalledTimes(2) expect(trigger2).toBeCalledTimes(2) }) + +test('multiple source update', () => { + const obs = observable({}) + + const fn1 = jest.fn() + const fn2 = jest.fn() + + autorun(() => { + const A = obs.A + const B = obs.B + if (A !== undefined && B !== undefined) { + obs.C = A / B + fn1() + } + }) + + autorun(() => { + const C = obs.C + const B = obs.B + if (C !== undefined && B !== undefined) { + obs.D = C * B + fn2() + } + }) + + obs.A = 1 + obs.B = 2 + + expect(fn1).toBeCalledTimes(1) + expect(fn2).toBeCalledTimes(1) +}) + +test('same source in nest update', () => { + const obs = observable({}) + + const fn1 = jest.fn() + + autorun(() => { + const B = obs.B + obs.B = 'B' + fn1() + return B + }) + + obs.B = 'B2' + + expect(fn1).toBeCalledTimes(2) +}) diff --git a/packages/reactive/src/__tests__/tracker.spec.ts b/packages/reactive/src/__tests__/tracker.spec.ts index c0da949ea81..3ce3bc17ef4 100644 --- a/packages/reactive/src/__tests__/tracker.spec.ts +++ b/packages/reactive/src/__tests__/tracker.spec.ts @@ -1,4 +1,4 @@ -import { Tracker, observable } from '../' +import { Tracker, observable, autorun } from '../' test('base tracker', () => { const obs = observable({}) @@ -18,6 +18,13 @@ test('base tracker', () => { tracker.dispose() }) +test('track argument is not a function', () => { + const scheduler = () => {} + const tracker = new Tracker(scheduler) + + tracker.track({}) +}) + test('nested tracker', () => { const obs = observable({}) const fn = jest.fn() @@ -91,3 +98,50 @@ test('shared scheduler with multi tracker(mock react strict mode)', () => { expect(scheduler1).toBeCalledTimes(1) expect(scheduler2).toBeCalledTimes(0) }) + +test('multiple source update', () => { + const obs = observable({}) + + const fn1 = jest.fn() + const fn2 = jest.fn() + + const view1 = () => { + const A = obs.A + const B = obs.B + if (A !== undefined && B !== undefined) { + obs.C = A / B + fn1() + } + } + const scheduler1 = () => { + tracker1.track(view1) + } + + const tracker1 = new Tracker(scheduler1) + + const view2 = () => { + const C = obs.C + const B = obs.B + if (C !== undefined && B !== undefined) { + obs.D = C * B + fn2() + } + } + const scheduler2 = () => { + tracker2.track(view2) + } + + const tracker2 = new Tracker(scheduler2) + + tracker1.track(view1) + tracker2.track(view2) + + obs.A = 1 + obs.B = 2 + + expect(fn1).toBeCalledTimes(1) + expect(fn2).toBeCalledTimes(1) + + tracker1.dispose() + tracker2.dispose() +}) diff --git a/packages/reactive/src/autorun.ts b/packages/reactive/src/autorun.ts index f2bbd97e4c1..8eaf00fdb94 100644 --- a/packages/reactive/src/autorun.ts +++ b/packages/reactive/src/autorun.ts @@ -19,7 +19,10 @@ interface IValue { export const autorun = (tracker: Reaction, name = 'AutoRun') => { const reaction: Reaction = () => { if (!isFn(tracker)) return - if (reaction._boundary > 0) return + + const updateKey = reaction._boundary.get(reaction._updateTarget) + if (updateKey && updateKey === reaction._updateKey) return + if (ReactionStack.indexOf(reaction) === -1) { releaseBindingReactions(reaction) try { @@ -28,9 +31,11 @@ export const autorun = (tracker: Reaction, name = 'AutoRun') => { tracker() } finally { ReactionStack.pop() - reaction._boundary++ + if (reaction._updateKey) { + reaction._boundary.set(reaction._updateTarget, reaction._updateKey) + } batchEnd() - reaction._boundary = 0 + reaction._boundary.clear() reaction._memos.cursor = 0 reaction._effects.cursor = 0 } @@ -46,10 +51,12 @@ export const autorun = (tracker: Reaction, name = 'AutoRun') => { cursor: 0, } } - reaction._boundary = 0 + + reaction._boundary = new Map() reaction._name = name cleanRefs() reaction() + return () => { disposeBindingReactions(reaction) disposeEffects(reaction) diff --git a/packages/reactive/src/reaction.ts b/packages/reactive/src/reaction.ts index 38444eae094..56d70256a1b 100644 --- a/packages/reactive/src/reaction.ts +++ b/packages/reactive/src/reaction.ts @@ -74,6 +74,8 @@ const runReactions = (target: any, key: PropertyKey) => { UntrackCount.value = 0 for (let i = 0, len = reactions.length; i < len; i++) { const reaction = reactions[i] + reaction._updateTarget = target + reaction._updateKey = key if (reaction._isComputed) { reaction._scheduler(reaction) } else if (isScopeBatching()) { diff --git a/packages/reactive/src/tracker.ts b/packages/reactive/src/tracker.ts index f6e2392e666..df857f59163 100644 --- a/packages/reactive/src/tracker.ts +++ b/packages/reactive/src/tracker.ts @@ -15,16 +15,19 @@ export class Tracker { name = 'TrackerReaction' ) { this.track._scheduler = (callback) => { - if (this.track._boundary === 0) this.dispose() + if (this.track._boundary.size === 0) this.dispose() if (isFn(callback)) scheduler(callback) } this.track._name = name - this.track._boundary = 0 + this.track._boundary = new Map() } track: Reaction = (tracker: Reaction) => { if (!isFn(tracker)) return this.results - if (this.track._boundary > 0) return + + const updateKey = this.track._boundary.get(this.track._updateTarget) + if (updateKey && updateKey === this.track._updateKey) return + if (ReactionStack.indexOf(this.track) === -1) { releaseBindingReactions(this.track) try { @@ -33,9 +36,14 @@ export class Tracker { this.results = tracker() } finally { ReactionStack.pop() - this.track._boundary++ + if (this.track._updateKey) { + this.track._boundary.set( + this.track._updateTarget, + this.track._updateKey + ) + } batchEnd() - this.track._boundary = 0 + this.track._boundary.clear() } } return this.results diff --git a/packages/reactive/src/types.ts b/packages/reactive/src/types.ts index f0fcca41185..c5996f03d4f 100644 --- a/packages/reactive/src/types.ts +++ b/packages/reactive/src/types.ts @@ -61,12 +61,14 @@ export type Dispose = () => void export type Effect = () => void | Dispose export type Reaction = ((...args: any[]) => any) & { - _boundary?: number + _boundary?: Map _name?: string _isComputed?: boolean _dirty?: boolean _context?: any _disposed?: boolean + _updateKey?: string + _updateTarget?: any _property?: PropertyKey _computesSet?: ArraySet _reactionsSet?: ArraySet