diff --git a/packages/core/src/models/Field.ts b/packages/core/src/models/Field.ts index 07f98d29169..8a8c0b6240a 100644 --- a/packages/core/src/models/Field.ts +++ b/packages/core/src/models/Field.ts @@ -9,14 +9,7 @@ import { ValidatorTriggerType, parseValidatorDescriptions, } from '@formily/validator' -import { - define, - observable, - reaction, - batch, - toJS, - action, -} from '@formily/reactive' +import { define, observable, batch, toJS, action } from '@formily/reactive' import { JSXComponent, LifeCycleTypes, @@ -56,6 +49,7 @@ import { initializeStart, initializeEnd, createChildrenFeedbackFilter, + createReaction, } from '../shared/internals' import { Form } from './Form' import { BaseField } from './BaseField' @@ -228,7 +222,7 @@ export class Field< protected makeReactive() { if (this.designable) return this.disposers.push( - reaction( + createReaction( () => this.value, (value) => { this.notify(LifeCycleTypes.ON_FIELD_VALUE_CHANGE) @@ -237,13 +231,13 @@ export class Field< } } ), - reaction( + createReaction( () => this.initialValue, () => { this.notify(LifeCycleTypes.ON_FIELD_INITIAL_VALUE_CHANGE) } ), - reaction( + createReaction( () => this.display, (display) => { const value = this.value @@ -266,7 +260,7 @@ export class Field< } } ), - reaction( + createReaction( () => this.pattern, (pattern) => { if (pattern !== 'editable') { diff --git a/packages/core/src/shared/internals.ts b/packages/core/src/shared/internals.ts index 900b1e32631..b5e1600be96 100644 --- a/packages/core/src/shared/internals.ts +++ b/packages/core/src/shared/internals.ts @@ -23,6 +23,8 @@ import { toJS, isObservable, DataChange, + reaction, + untracked, } from '@formily/reactive' import { Field, ArrayField, Form, ObjectField } from '../models' import { @@ -1031,6 +1033,13 @@ export const createReactions = (field: GeneralField) => { }) } +export const createReaction = ( + tracker: () => T, + scheduler?: (value: T) => void +) => { + return reaction(tracker, untracked.bound(scheduler)) +} + export const initializeStart = () => { GlobalState.initializing = true } diff --git a/packages/reactive/src/__tests__/batch.spec.ts b/packages/reactive/src/__tests__/batch.spec.ts index ad908531e4e..a0a94f81960 100644 --- a/packages/reactive/src/__tests__/batch.spec.ts +++ b/packages/reactive/src/__tests__/batch.spec.ts @@ -1,4 +1,4 @@ -import { observable, batch, autorun } from '..' +import { observable, batch, autorun, reaction } from '..' import { define } from '../model' describe('normal batch', () => { @@ -539,3 +539,54 @@ describe('batch endpoint', () => { expect(tokens).toEqual(['endpoint']) }) }) + +test('reaction collect in batch valid', () => { + const obs = observable({ + aa: 11, + bb: 22, + cc: 33, + }) + reaction( + () => obs.aa, + () => { + void obs.cc + } + ) + const fn = jest.fn() + + autorun(() => { + batch.scope(() => { + obs.aa = obs.bb + }) + fn() + }) + + obs.bb = 44 + expect(fn).toBeCalledTimes(2) +}) + +test('reaction collect in batch invalid', () => { + const obs = observable({ + aa: 11, + bb: 22, + cc: 33, + }) + reaction( + () => obs.aa, + () => { + void obs.cc + } + ) + const fn = jest.fn() + + autorun(() => { + batch.scope(() => { + obs.aa = obs.bb + }) + fn() + }) + + obs.bb = 44 + obs.cc = 55 + expect(fn).toBeCalledTimes(3) +}) diff --git a/packages/reactive/src/autorun.ts b/packages/reactive/src/autorun.ts index 76140d15eaf..f2bbd97e4c1 100644 --- a/packages/reactive/src/autorun.ts +++ b/packages/reactive/src/autorun.ts @@ -5,8 +5,6 @@ import { releaseBindingReactions, disposeEffects, hasDepsChange, - untrackStart, - untrackEnd, } from './reaction' import { isFn } from './checkers' import { ReactionStack } from './environment' @@ -119,12 +117,11 @@ export const reaction = ( const fireAction = () => { try { - untrackStart() + //如果untrack的话,会导致用户如果在scheduler里同步调用setState影响下次React渲染的依赖收集 batchStart() if (isFn(subscriber)) subscriber(value.currentValue, value.oldValue) } finally { batchEnd() - untrackEnd() } }