All files / reactivity/src effectScope.ts

95.74% Statements 45/47
96% Branches 24/25
100% Functions 10/10
95.74% Lines 45/47

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158  101x       101x       1748x       1748x       1748x                                     1748x 1748x 1748x 2x               1412x       23x 22x 22x 22x 22x   22x   1x 1x                 2814x               2796x       247x   247x 261x   247x 4x   247x 1x 1x       247x   1x 1x         247x 247x                         101x 2x     101x       1705x 1410x                   101x 147x                   101x 5x 4x 1x 1x            
import { ReactiveEffect } from './effect'
import { warn } from './warning'
 
let activeEffectScope: EffectScope | undefined
 
export class EffectScope {
  /**
   * @internal
   */
  private _active = true
  /**
   * @internal
   */
  effects: ReactiveEffect[] = []
  /**
   * @internal
   */
  cleanups: (() => void)[] = []
 
  /**
   * only assigned by undetached scope
   * @internal
   */
  parent: EffectScope | undefined
  /**
   * record undetached scopes
   * @internal
   */
  scopes: EffectScope[] | undefined
  /**
   * track a child scope's index in its parent's scopes array for optimized
   * removal
   * @internal
   */
  private index: number | undefined
 
  constructor(public detached = false) {
    this.parent = activeEffectScope
    if (!detached && activeEffectScope) {
      this.index =
        (activeEffectScope.scopes || (activeEffectScope.scopes = [])).push(
          this
        ) - 1
    }
  }
 
  get active() {
    return this._active
  }
 
  run<T>(fn: () => T): T | undefined {
    if (this._active) {
      const currentEffectScope = activeEffectScope
      try {
        activeEffectScope = this
        return fn()
      } finally {
        activeEffectScope = currentEffectScope
      }
    } else if (__DEV__) {
      warn(`cannot run an inactive effect scope.`)
    }
  }
 
  /**
   * This should only be called on non-detached scopes
   * @internal
   */
  on() {
    activeEffectScope = this
  }
 
  /**
   * This should only be called on non-detached scopes
   * @internal
   */
  off() {
    activeEffectScope = this.parent
  }
 
  stop(fromParent?: boolean) {
    if (this._active) {
      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) {
        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.detached && this.parent && !fromParent) {
        // optimized O(1) removal
        const last = this.parent.scopes!.pop()
        Iif (last && last !== this) {
          this.parent.scopes![this.index!] = last
          last.index = this.index!
        }
      }
      this.parent = undefined
      this._active = false
    }
  }
}
 
/**
 * Creates an effect scope object which can capture the reactive effects
 * (i.e. computed and watchers) created within it so that these effects
 * can be disposed of together.
 *
 * @see {@link https://github.com/vuejs/rfcs/pull/212}
 * @param detached Can be used to create a "detached" effect scope.
 */
export function effectScope(detached?: boolean) {
  return new EffectScope(detached)
}
 
export function recordEffectScope(
  effect: ReactiveEffect,
  scope: EffectScope | undefined = activeEffectScope
) {
  if (scope && scope.active) {
    scope.effects.push(effect)
  }
}
 
/**
 * Provides access to the current effect scope.
 *
 * This may be undefined at times when there's no effect active effect scope at
 * this very moment.
 */
export function getCurrentScope() {
  return activeEffectScope
}
 
/**
 * Allows to add a callback to the current effect scope's cleanup.
 *
 * If there is no active effect scope at the moment, this will do nothing.
 *
 * @param fn The callback function to attach to the scope's cleanup.
 */
export function onScopeDispose(fn: () => void) {
  if (activeEffectScope) {
    activeEffectScope.cleanups.push(fn)
  } else if (__DEV__) {
    warn(
      `onScopeDispose() is called when there is no active effect scope` +
        ` to be associated with.`
    )
  }
}