]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
perf(reactivity): optimize effect/effectScope active state tracking
authorEvan You <yyx990803@gmail.com>
Fri, 28 Jan 2022 10:35:09 +0000 (18:35 +0800)
committerEvan You <yyx990803@gmail.com>
Fri, 28 Jan 2022 10:35:09 +0000 (18:35 +0800)
packages/reactivity/src/effect.ts
packages/reactivity/src/effectScope.ts
packages/reactivity/src/ref.ts

index 3c21ab7150f058e9abcd822453903890d99d4c3c..892eef102ada40c6a9a38bce9aea8b76b668d0e4 100644 (file)
@@ -45,7 +45,6 @@ export type DebuggerEventExtraInfo = {
   oldTarget?: Map<any, any> | Set<any>
 }
 
-const effectStack: ReactiveEffect[] = []
 let activeEffect: ReactiveEffect | undefined
 
 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> {
   active = true
   deps: Dep[] = []
+  parent: ReactiveEffect | undefined = undefined
 
   /**
    * Can be attached after creation
@@ -74,7 +74,7 @@ export class ReactiveEffect<T = any> {
   constructor(
     public fn: () => T,
     public scheduler: EffectScheduler | null = null,
-    scope?: EffectScope | null
+    scope?: EffectScope
   ) {
     recordEffectScope(this, scope)
   }
@@ -83,31 +83,37 @@ export class ReactiveEffect<T = any> {
     if (!this.active) {
       return this.fn()
     }
-    if (!effectStack.length || !effectStack.includes(this)) {
-      try {
-        effectStack.push((activeEffect = this))
-        enableTracking()
+    let parent: ReactiveEffect | undefined = activeEffect
+    let lastShouldTrack = shouldTrack
+    while (parent) {
+      if (parent === this) {
+        return
+      }
+      parent = parent.parent
+    }
+    try {
+      this.parent = activeEffect
+      activeEffect = this
+      shouldTrack = true
 
-        trackOpBit = 1 << ++effectTrackDepth
+      trackOpBit = 1 << ++effectTrackDepth
 
-        if (effectTrackDepth <= maxMarkerBits) {
-          initDepMarkers(this)
-        } else {
-          cleanupEffect(this)
-        }
-        return this.fn()
-      } finally {
-        if (effectTrackDepth <= maxMarkerBits) {
-          finalizeDepMarkers(this)
-        }
+      if (effectTrackDepth <= maxMarkerBits) {
+        initDepMarkers(this)
+      } else {
+        cleanupEffect(this)
+      }
+      return this.fn()
+    } finally {
+      if (effectTrackDepth <= maxMarkerBits) {
+        finalizeDepMarkers(this)
+      }
 
-        trackOpBit = 1 << --effectTrackDepth
+      trackOpBit = 1 << --effectTrackDepth
 
-        resetTracking()
-        effectStack.pop()
-        const n = effectStack.length
-        activeEffect = n > 0 ? effectStack[n - 1] : undefined
-      }
+      activeEffect = this.parent
+      shouldTrack = lastShouldTrack
+      this.parent = undefined
     }
   }
 
@@ -214,7 +220,7 @@ export function track(target: object, type: TrackOpTypes, key: unknown) {
 }
 
 export function isTracking() {
-  return shouldTrack && activeEffect !== undefined
+  return shouldTrack && !!activeEffect
 }
 
 export function trackEffects(
index 864443f85b8de02552fba83be17de368db3d74a6..7c3dbc989d17ede4deac135072768103696b6db3 100644 (file)
@@ -2,7 +2,6 @@ import { ReactiveEffect } from './effect'
 import { warn } from './warning'
 
 let activeEffectScope: EffectScope | undefined
-const effectScopeStack: EffectScope[] = []
 
 export class EffectScope {
   active = true
@@ -42,24 +41,29 @@ export class EffectScope {
 
   on() {
     if (this.active) {
-      effectScopeStack.push(this)
       activeEffectScope = this
     }
   }
 
   off() {
     if (this.active) {
-      effectScopeStack.pop()
-      activeEffectScope = effectScopeStack[effectScopeStack.length - 1]
+      activeEffectScope = this.parent
     }
   }
 
   stop(fromParent?: boolean) {
     if (this.active) {
-      this.effects.forEach(e => e.stop())
-      this.cleanups.forEach(cleanup => cleanup())
+      let i, l
+      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) {
-        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
       if (this.parent && !fromParent) {
@@ -81,9 +85,8 @@ export function effectScope(detached?: boolean) {
 
 export function recordEffectScope(
   effect: ReactiveEffect,
-  scope?: EffectScope | null
+  scope: EffectScope | undefined = activeEffectScope
 ) {
-  scope = scope || activeEffectScope
   if (scope && scope.active) {
     scope.effects.push(effect)
   }
index 20aa6c0a9893be9cbf5b022de87085491f826539..d9ff17ac0981bb933712098fc96d2b291a18c6ae 100644 (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(r: any): r is Ref {
-  return Boolean(r && r.__v_isRef === true)
+  return !!(r && r.__v_isRef === true)
 }
 
 export function ref<T extends object>(