Skip to content

Commit b995220

Browse files
committed
feat(reactive): optimize performance, computed deps change won't directly trigger upstream reaction
1 parent e8a551a commit b995220

File tree

3 files changed

+98
-20
lines changed

3 files changed

+98
-20
lines changed

packages/reactive/src/annotations/computed.ts

Lines changed: 61 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
batchStart,
1212
batchEnd,
1313
releaseBindingReactions,
14+
getReactionsFromTargetKey,
1415
} from '../reaction'
1516

1617
interface IValue<T = any> {
@@ -91,18 +92,56 @@ export const computed: IComputed = createAnnotation(
9192
}
9293
}
9394
}
95+
9496
reaction._name = 'ComputedReaction'
9597
reaction._scheduler = () => {
96-
reaction._dirty = true
97-
runReactionsFromTargetKey({
98-
target: context,
99-
key: property,
100-
value: store.value,
101-
type: 'set',
102-
})
98+
if (!reaction._dirty_scheduler) return
99+
reaction._dirty_scheduler = false
100+
101+
if (!reaction._dirty) {
102+
runReactionsFromTargetKey({
103+
target: context,
104+
key: property,
105+
value: store.value,
106+
type: 'set',
107+
})
108+
return
109+
}
110+
111+
const deps = getReactionsFromTargetKey(context, property)
112+
113+
if (!deps.length) return
114+
115+
if (deps.every((dep) => dep._isComputed)) {
116+
// all deps are computed reactions, so should dirty the upstream computed reactions
117+
runReactionsFromTargetKey({
118+
target: context,
119+
key: property,
120+
value: store.value,
121+
type: 'set',
122+
})
123+
return
124+
}
125+
126+
const currentValue = store.value
127+
reaction()
128+
reaction._dirty = false
129+
const newValue = store.value
130+
if (newValue !== currentValue) {
131+
runReactionsFromTargetKey({
132+
target: context,
133+
key: property,
134+
value: store.value,
135+
type: 'set',
136+
})
137+
}
103138
}
139+
104140
reaction._isComputed = true
141+
// is need to re calculate
105142
reaction._dirty = true
143+
// is need to schedule to notice upstream reactions
144+
reaction._dirty_scheduler = true
106145
reaction._context = context
107146
reaction._property = property
108147

@@ -113,18 +152,25 @@ export const computed: IComputed = createAnnotation(
113152
if (!isUntracking()) {
114153
//如果允许untracked过程中收集依赖,那么永远不会存在绑定,因为_dirty已经设置为false
115154
if (reaction._dirty) {
155+
// if the value is used in batch function, it will directly execute and set dirty to false
156+
const currentValue = store.value
116157
reaction()
117158
reaction._dirty = false
159+
const newValue = store.value
160+
if (newValue === currentValue) {
161+
// no need to schedule
162+
reaction._dirty_scheduler = false
163+
}
118164
}
119-
} else {
120-
compute()
165+
bindTargetKeyWithCurrentReaction({
166+
target: context,
167+
key: property,
168+
type: 'get',
169+
})
170+
return store.value
121171
}
122-
bindTargetKeyWithCurrentReaction({
123-
target: context,
124-
key: property,
125-
type: 'get',
126-
})
127-
return store.value
172+
173+
return descriptor.get?.call(context)
128174
}
129175

130176
function set(value: any) {

packages/reactive/src/environment.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ export const BatchScope = { value: false }
1515
export const DependencyCollected = { value: false }
1616
export const PendingReactions = new ArraySet<Reaction>()
1717
export const PendingScopeReactions = new ArraySet<Reaction>()
18+
export const PendingComputedReactions = new ArraySet<Reaction>()
19+
export const PendingScopeComputedReactions = new ArraySet<Reaction>()
1820
export const BatchEndpoints = new ArraySet<() => void>()
1921
export const ObserverListeners = new ArraySet<ObservableListener>()
2022
export const MakeObModelSymbol = Symbol('MakeObModelSymbol')

packages/reactive/src/reaction.ts

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import {
1212
UntrackCount,
1313
BatchScope,
1414
ObserverListeners,
15+
PendingComputedReactions,
16+
PendingScopeComputedReactions,
1517
} from './environment'
1618

1719
const ITERATION_KEY = Symbol('iteration key')
@@ -52,7 +54,7 @@ const addReactionsMapToReaction = (
5254
return bindSet
5355
}
5456

55-
const getReactionsFromTargetKey = (target: any, key: PropertyKey) => {
57+
export const getReactionsFromTargetKey = (target: any, key: PropertyKey) => {
5658
const reactionsMap = RawReactionsMap.get(target)
5759
const reactions = []
5860
if (reactionsMap) {
@@ -75,7 +77,15 @@ const runReactions = (target: any, key: PropertyKey) => {
7577
for (let i = 0, len = reactions.length; i < len; i++) {
7678
const reaction = reactions[i]
7779
if (reaction._isComputed) {
78-
reaction._scheduler(reaction)
80+
reaction._dirty = true
81+
reaction._dirty_scheduler = true
82+
if (isScopeBatching()) {
83+
PendingScopeComputedReactions.add(reaction)
84+
} else if (isBatching()) {
85+
PendingComputedReactions.add(reaction)
86+
} else {
87+
reaction._scheduler(reaction)
88+
}
7989
} else if (isScopeBatching()) {
8090
PendingScopeReactions.add(reaction)
8191
} else if (isBatching()) {
@@ -182,13 +192,18 @@ export const batchStart = () => {
182192
}
183193

184194
export const batchEnd = () => {
185-
BatchCount.value--
186-
if (BatchCount.value === 0) {
195+
if (BatchCount.value === 1) {
187196
const prevUntrackCount = UntrackCount.value
188197
UntrackCount.value = 0
198+
executePendingComputedReactions()
199+
200+
BatchCount.value--
201+
189202
executePendingReactions()
190203
executeBatchEndpoints()
191204
UntrackCount.value = prevUntrackCount
205+
} else {
206+
BatchCount.value--
192207
}
193208
}
194209

@@ -198,8 +213,15 @@ export const batchScopeStart = () => {
198213

199214
export const batchScopeEnd = () => {
200215
const prevUntrackCount = UntrackCount.value
201-
BatchScope.value = false
202216
UntrackCount.value = 0
217+
218+
PendingScopeComputedReactions.batchDelete((reaction) => {
219+
if (isFn(reaction._scheduler)) {
220+
reaction._scheduler(reaction)
221+
}
222+
})
223+
224+
BatchScope.value = false
203225
PendingScopeReactions.batchDelete((reaction) => {
204226
if (isFn(reaction._scheduler)) {
205227
reaction._scheduler(reaction)
@@ -224,6 +246,14 @@ export const isScopeBatching = () => BatchScope.value
224246

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

249+
export const executePendingComputedReactions = () => {
250+
PendingComputedReactions.batchDelete((reaction) => {
251+
if (isFn(reaction._scheduler)) {
252+
reaction._scheduler(reaction)
253+
}
254+
})
255+
}
256+
227257
export const executePendingReactions = () => {
228258
PendingReactions.batchDelete((reaction) => {
229259
if (isFn(reaction._scheduler)) {

0 commit comments

Comments
 (0)