Skip to content

Commit 2993a24

Browse files
committed
perf(reactivity): optimize effect/effectScope active state tracking
1 parent 6b68898 commit 2993a24

File tree

3 files changed

+43
-34
lines changed

3 files changed

+43
-34
lines changed

packages/reactivity/src/effect.ts

+30-24
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@ export type DebuggerEventExtraInfo = {
4545
oldTarget?: Map<any, any> | Set<any>
4646
}
4747

48-
const effectStack: ReactiveEffect[] = []
4948
let activeEffect: ReactiveEffect | undefined
5049

5150
export const ITERATE_KEY = Symbol(__DEV__ ? 'iterate' : '')
@@ -54,6 +53,7 @@ export const MAP_KEY_ITERATE_KEY = Symbol(__DEV__ ? 'Map key iterate' : '')
5453
export class ReactiveEffect<T = any> {
5554
active = true
5655
deps: Dep[] = []
56+
parent: ReactiveEffect | undefined = undefined
5757

5858
/**
5959
* Can be attached after creation
@@ -74,7 +74,7 @@ export class ReactiveEffect<T = any> {
7474
constructor(
7575
public fn: () => T,
7676
public scheduler: EffectScheduler | null = null,
77-
scope?: EffectScope | null
77+
scope?: EffectScope
7878
) {
7979
recordEffectScope(this, scope)
8080
}
@@ -83,31 +83,37 @@ export class ReactiveEffect<T = any> {
8383
if (!this.active) {
8484
return this.fn()
8585
}
86-
if (!effectStack.length || !effectStack.includes(this)) {
87-
try {
88-
effectStack.push((activeEffect = this))
89-
enableTracking()
86+
let parent: ReactiveEffect | undefined = activeEffect
87+
let lastShouldTrack = shouldTrack
88+
while (parent) {
89+
if (parent === this) {
90+
return
91+
}
92+
parent = parent.parent
93+
}
94+
try {
95+
this.parent = activeEffect
96+
activeEffect = this
97+
shouldTrack = true
9098

91-
trackOpBit = 1 << ++effectTrackDepth
99+
trackOpBit = 1 << ++effectTrackDepth
92100

93-
if (effectTrackDepth <= maxMarkerBits) {
94-
initDepMarkers(this)
95-
} else {
96-
cleanupEffect(this)
97-
}
98-
return this.fn()
99-
} finally {
100-
if (effectTrackDepth <= maxMarkerBits) {
101-
finalizeDepMarkers(this)
102-
}
101+
if (effectTrackDepth <= maxMarkerBits) {
102+
initDepMarkers(this)
103+
} else {
104+
cleanupEffect(this)
105+
}
106+
return this.fn()
107+
} finally {
108+
if (effectTrackDepth <= maxMarkerBits) {
109+
finalizeDepMarkers(this)
110+
}
103111

104-
trackOpBit = 1 << --effectTrackDepth
112+
trackOpBit = 1 << --effectTrackDepth
105113

106-
resetTracking()
107-
effectStack.pop()
108-
const n = effectStack.length
109-
activeEffect = n > 0 ? effectStack[n - 1] : undefined
110-
}
114+
activeEffect = this.parent
115+
shouldTrack = lastShouldTrack
116+
this.parent = undefined
111117
}
112118
}
113119

@@ -214,7 +220,7 @@ export function track(target: object, type: TrackOpTypes, key: unknown) {
214220
}
215221

216222
export function isTracking() {
217-
return shouldTrack && activeEffect !== undefined
223+
return shouldTrack && !!activeEffect
218224
}
219225

220226
export function trackEffects(

packages/reactivity/src/effectScope.ts

+12-9
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import { ReactiveEffect } from './effect'
22
import { warn } from './warning'
33

44
let activeEffectScope: EffectScope | undefined
5-
const effectScopeStack: EffectScope[] = []
65

76
export class EffectScope {
87
active = true
@@ -42,24 +41,29 @@ export class EffectScope {
4241

4342
on() {
4443
if (this.active) {
45-
effectScopeStack.push(this)
4644
activeEffectScope = this
4745
}
4846
}
4947

5048
off() {
5149
if (this.active) {
52-
effectScopeStack.pop()
53-
activeEffectScope = effectScopeStack[effectScopeStack.length - 1]
50+
activeEffectScope = this.parent
5451
}
5552
}
5653

5754
stop(fromParent?: boolean) {
5855
if (this.active) {
59-
this.effects.forEach(e => e.stop())
60-
this.cleanups.forEach(cleanup => cleanup())
56+
let i, l
57+
for (i = 0, l = this.effects.length; i < l; i++) {
58+
this.effects[i].stop()
59+
}
60+
for (i = 0, l = this.cleanups.length; i < l; i++) {
61+
this.cleanups[i]()
62+
}
6163
if (this.scopes) {
62-
this.scopes.forEach(e => e.stop(true))
64+
for (i = 0, l = this.scopes.length; i < l; i++) {
65+
this.scopes[i].stop(true)
66+
}
6367
}
6468
// nested scope, dereference from parent to avoid memory leaks
6569
if (this.parent && !fromParent) {
@@ -81,9 +85,8 @@ export function effectScope(detached?: boolean) {
8185

8286
export function recordEffectScope(
8387
effect: ReactiveEffect,
84-
scope?: EffectScope | null
88+
scope: EffectScope | undefined = activeEffectScope
8589
) {
86-
scope = scope || activeEffectScope
8790
if (scope && scope.active) {
8891
scope.effects.push(effect)
8992
}

packages/reactivity/src/ref.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ export function triggerRefValue(ref: RefBase<any>, newVal?: any) {
5959

6060
export function isRef<T>(r: Ref<T> | unknown): r is Ref<T>
6161
export function isRef(r: any): r is Ref {
62-
return Boolean(r && r.__v_isRef === true)
62+
return !!(r && r.__v_isRef === true)
6363
}
6464

6565
export function ref<T extends object>(

0 commit comments

Comments
 (0)