perf(reactivity): optimize effect/effectScope active state tracking

This commit is contained in:
Evan You 2022-01-28 18:35:09 +08:00
parent 6b6889852f
commit 2993a24618
3 changed files with 46 additions and 37 deletions

View File

@ -45,7 +45,6 @@ export type DebuggerEventExtraInfo = {
oldTarget?: Map<any, any> | Set<any> oldTarget?: Map<any, any> | Set<any>
} }
const effectStack: ReactiveEffect[] = []
let activeEffect: ReactiveEffect | undefined let activeEffect: ReactiveEffect | undefined
export const ITERATE_KEY = Symbol(__DEV__ ? 'iterate' : '') export const ITERATE_KEY = Symbol(__DEV__ ? 'iterate' : '')
@ -54,6 +53,7 @@ export const MAP_KEY_ITERATE_KEY = Symbol(__DEV__ ? 'Map key iterate' : '')
export class ReactiveEffect<T = any> { export class ReactiveEffect<T = any> {
active = true active = true
deps: Dep[] = [] deps: Dep[] = []
parent: ReactiveEffect | undefined = undefined
/** /**
* Can be attached after creation * Can be attached after creation
@ -74,7 +74,7 @@ export class ReactiveEffect<T = any> {
constructor( constructor(
public fn: () => T, public fn: () => T,
public scheduler: EffectScheduler | null = null, public scheduler: EffectScheduler | null = null,
scope?: EffectScope | null scope?: EffectScope
) { ) {
recordEffectScope(this, scope) recordEffectScope(this, scope)
} }
@ -83,10 +83,18 @@ export class ReactiveEffect<T = any> {
if (!this.active) { if (!this.active) {
return this.fn() return this.fn()
} }
if (!effectStack.length || !effectStack.includes(this)) { let parent: ReactiveEffect | undefined = activeEffect
let lastShouldTrack = shouldTrack
while (parent) {
if (parent === this) {
return
}
parent = parent.parent
}
try { try {
effectStack.push((activeEffect = this)) this.parent = activeEffect
enableTracking() activeEffect = this
shouldTrack = true
trackOpBit = 1 << ++effectTrackDepth trackOpBit = 1 << ++effectTrackDepth
@ -103,11 +111,9 @@ export class ReactiveEffect<T = any> {
trackOpBit = 1 << --effectTrackDepth trackOpBit = 1 << --effectTrackDepth
resetTracking() activeEffect = this.parent
effectStack.pop() shouldTrack = lastShouldTrack
const n = effectStack.length this.parent = undefined
activeEffect = n > 0 ? effectStack[n - 1] : undefined
}
} }
} }
@ -214,7 +220,7 @@ export function track(target: object, type: TrackOpTypes, key: unknown) {
} }
export function isTracking() { export function isTracking() {
return shouldTrack && activeEffect !== undefined return shouldTrack && !!activeEffect
} }
export function trackEffects( export function trackEffects(

View File

@ -2,7 +2,6 @@ import { ReactiveEffect } from './effect'
import { warn } from './warning' import { warn } from './warning'
let activeEffectScope: EffectScope | undefined let activeEffectScope: EffectScope | undefined
const effectScopeStack: EffectScope[] = []
export class EffectScope { export class EffectScope {
active = true active = true
@ -42,24 +41,29 @@ export class EffectScope {
on() { on() {
if (this.active) { if (this.active) {
effectScopeStack.push(this)
activeEffectScope = this activeEffectScope = this
} }
} }
off() { off() {
if (this.active) { if (this.active) {
effectScopeStack.pop() activeEffectScope = this.parent
activeEffectScope = effectScopeStack[effectScopeStack.length - 1]
} }
} }
stop(fromParent?: boolean) { stop(fromParent?: boolean) {
if (this.active) { if (this.active) {
this.effects.forEach(e => e.stop()) let i, l
this.cleanups.forEach(cleanup => cleanup()) for (i = 0, l = this.effects.length; i < l; i++) {
this.effects[i].stop()
}
for (i = 0, l = this.cleanups.length; i < l; i++) {
this.cleanups[i]()
}
if (this.scopes) { if (this.scopes) {
this.scopes.forEach(e => e.stop(true)) for (i = 0, l = this.scopes.length; i < l; i++) {
this.scopes[i].stop(true)
}
} }
// nested scope, dereference from parent to avoid memory leaks // nested scope, dereference from parent to avoid memory leaks
if (this.parent && !fromParent) { if (this.parent && !fromParent) {
@ -81,9 +85,8 @@ export function effectScope(detached?: boolean) {
export function recordEffectScope( export function recordEffectScope(
effect: ReactiveEffect, effect: ReactiveEffect,
scope?: EffectScope | null scope: EffectScope | undefined = activeEffectScope
) { ) {
scope = scope || activeEffectScope
if (scope && scope.active) { if (scope && scope.active) {
scope.effects.push(effect) scope.effects.push(effect)
} }

View File

@ -59,7 +59,7 @@ export function triggerRefValue(ref: RefBase<any>, newVal?: any) {
export function isRef<T>(r: Ref<T> | unknown): r is Ref<T> export function isRef<T>(r: Ref<T> | unknown): r is Ref<T>
export function isRef(r: any): r is Ref { export function isRef(r: any): r is Ref {
return Boolean(r && r.__v_isRef === true) return !!(r && r.__v_isRef === true)
} }
export function ref<T extends object>( export function ref<T extends object>(