Skip to content

Commit

Permalink
feat(reactive): recover batch.scope
Browse files Browse the repository at this point in the history
  • Loading branch information
janryWang committed Apr 8, 2021
1 parent db82802 commit aeeb9f9
Show file tree
Hide file tree
Showing 8 changed files with 101 additions and 19 deletions.
1 change: 1 addition & 0 deletions packages/.eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
"@typescript-eslint/no-unused-vars": 0,
"@typescript-eslint/no-namespace": 0,
"@typescript-eslint/ban-types": 0,
"@typescript-eslint/adjacent-overload-signatures": 0,
"@typescript-eslint/explicit-module-boundary-types": 0,
"@typescript-eslint/no-empty-function": 0,
"no-console": [
Expand Down
8 changes: 6 additions & 2 deletions packages/core/src/models/Field.ts
Original file line number Diff line number Diff line change
Expand Up @@ -651,8 +651,12 @@ export class Field<

onInit = () => {
this.initialized = true
initFieldValue(this)
initFieldUpdate(this)
batch.scope(() => {
initFieldValue(this)
})
batch.scope(() => {
initFieldUpdate(this)
})
this.form.notify(LifeCycleTypes.ON_FIELD_INIT, this)
}

Expand Down
4 changes: 3 additions & 1 deletion packages/core/src/models/VoidField.ts
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,9 @@ export class VoidField<Decorator = any, Component = any, TextType = any> {

onInit = () => {
this.initialized = true
initFieldUpdate(this)
batch.scope(() => {
initFieldUpdate(this)
})
this.form.notify(LifeCycleTypes.ON_FIELD_INIT, this)
}

Expand Down
13 changes: 10 additions & 3 deletions packages/reactive/docs/api/batch.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@

## 描述

接收一个操作函数,并立即执行,执行过程以批量模式执行,也就是每次操作函数执行一次,Reaction 只会响应一次,如果 batch 存在嵌套,则会以最顶层的 batch 结束为响应时机,同时 batch 还能在 define 中以 annotation 的方式标注某个方法是 batch 模式。
接收一个操作函数,并立即执行,执行过程以批量模式执行,也就是每次操作函数执行一次,Reaction 只会响应一次,如果 batch 存在嵌套,则会以最顶层的 batch 结束为响应时机,相反,batch.scope,则不会等最顶层的 batch 执行完成才响应,而是当前 scope 执行结束立即响应,同时 batch 还能在 define 中以 annotation 的方式标注某个方法是 batch 模式。

## 签名

```ts
interface batch<T extends (...args: any[]) => any> {
(callback?: T): ReturnType<T>
scope<T extends (...args: any[]) => any>(callback?: T): ReturnType<T>
}
```

Expand All @@ -20,11 +21,17 @@ import { observable, autorun, batch } from '@formily/reactive'
const obs = observable({})

autorun(() => {
console.log(obs.aa, obs.bb)
console.log(obs.aa, obs.bb, obs.cc, obs.dd)
})

batch(() => {
obs.aa = 123
batch.scope(() => {
obs.aa = 123
})
batch.scope(() => {
obs.cc = 'ccccc'
})
obs.bb = 321
obs.dd = 'dddd'
})
```
24 changes: 24 additions & 0 deletions packages/reactive/src/__tests__/batch.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,27 @@ test('action', () => {
batch()
expect(handler).toBeCalledTimes(4)
})


test('batch scope', () => {
const obs = observable<any>({})

const handler = jest.fn()

autorun(() => {
handler(obs.aa, obs.bb, obs.cc, obs.dd)
})

batch(() => {
batch.scope(() => {
obs.aa = 123
})
batch.scope(() => {
obs.cc = 'ccccc'
})
obs.bb = 321
obs.dd = 'ddddd'
})

expect(handler).toBeCalledTimes(4)
})
42 changes: 30 additions & 12 deletions packages/reactive/src/batch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,28 @@ import { isFn } from '@formily/shared'
import {
batchStart,
batchEnd,
batchScopeStart,
batchScopeEnd,
} from './reaction'
import { createAnnotation } from './internals'
import { MakeObservableSymbol } from './environment'

const createBatchAnnotation = <F extends (...args: any[]) => any>(method: F) =>
createAnnotation(({ target, key, value }) => {
const action = <T extends (...args: any[]) => any>(callback?: T) => {
return function (...args: Parameters<T>): ReturnType<T> {
return method(() =>
isFn(callback) ? callback.apply(this, args) : undefined
)
}
}
if (target) {
target[key] = action(target[key])
return target
}
return action(value)
})

export const batch = <T>(callback?: () => T) => {
let result: T = null
try {
Expand All @@ -19,20 +37,20 @@ export const batch = <T>(callback?: () => T) => {
return result
}

export const action = createBatchAnnotation(batch)

export const action = createAnnotation(({ target, key, value }) => {
const action = <T extends (...args: any[]) => any>(callback?: T) => {
return function (...args: Parameters<T>): ReturnType<T> {
return batch(() =>
isFn(callback) ? callback.apply(this, args) : undefined
)
batch.scope = <T>(callback?: () => T) => {
let result: T = null
try {
batchScopeStart()
if (isFn(callback)) {
result = callback()
}
} finally {
batchScopeEnd()
}
if (target) {
target[key] = action(target[key])
return target
}
return action(value)
})
return result
}

batch[MakeObservableSymbol] = action
batch.scope[MakeObservableSymbol] = createBatchAnnotation(batch.scope)
2 changes: 2 additions & 0 deletions packages/reactive/src/environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,7 @@ export const ReactionComputeds = new WeakMap<Reaction, Set<Reaction>>()
export const ReactionStack: Reaction[] = []
export const BatchCount = { value: 0 }
export const UntrackCount = { value: 0 }
export const BatchScope = { value: false }
export const PendingReactions = new Set<Reaction>()
export const PendingScopeReactions = new Set<Reaction>()
export const MakeObservableSymbol = Symbol('MakeObservableSymbol')
26 changes: 25 additions & 1 deletion packages/reactive/src/reaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,14 @@ import {
ReactionStack,
TargetKeysReactions,
ReactionKeysReactions,
PendingScopeReactions,
PendingReactions,
ReactionComputeds,
BatchCount,
UntrackCount,
ProxyRaw,
RawNode,
BatchScope,
} from './environment'

const ITERATION_KEY = Symbol('iteration key')
Expand Down Expand Up @@ -76,7 +78,11 @@ const runReactions = (target: any, key: PropertyKey) => {
reactions.forEach((reaction) => {
if (reaction._isComputed) {
reaction._scheduler(reaction)
} else if (isBatching()) {
} else if (isScopeBatching()) {
if (!PendingScopeReactions.has(reaction)) {
PendingScopeReactions.add(reaction)
}
} else if (isBatching()) {
if (!PendingReactions.has(reaction)) {
PendingReactions.add(reaction)
}
Expand Down Expand Up @@ -223,6 +229,22 @@ export const batchEnd = () => {
}
}

export const batchScopeStart = () => {
BatchScope.value = true
}

export const batchScopeEnd = () => {
BatchScope.value = false
PendingScopeReactions.forEach((reaction) => {
PendingScopeReactions.delete(reaction)
if (isFn(reaction._scheduler)) {
reaction._scheduler(reaction)
} else {
reaction()
}
})
}

export const untrackStart = () => {
UntrackCount.value++
}
Expand All @@ -233,6 +255,8 @@ export const untrackEnd = () => {

export const isBatching = () => BatchCount.value > 0

export const isScopeBatching = () => BatchScope.value

export const isUntracking = () => UntrackCount.value > 0

export const excutePendingReactions = () => {
Expand Down

0 comments on commit aeeb9f9

Please sign in to comment.