| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202 |
- import type { ReactiveEffect } from './effect'
- import { warn } from './warning'
- export let activeEffectScope: EffectScope | undefined
- export class EffectScope {
- /**
- * @internal
- */
- private _active = true
- /**
- * @internal track `on` calls, allow `on` call multiple times
- */
- private _on = 0
- /**
- * @internal
- */
- effects: ReactiveEffect[] = []
- /**
- * @internal
- */
- cleanups: (() => void)[] = []
- private _isPaused = false
- /**
- * 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(): boolean {
- return this._active
- }
- pause(): void {
- if (this._active) {
- this._isPaused = true
- let i, l
- if (this.scopes) {
- for (i = 0, l = this.scopes.length; i < l; i++) {
- this.scopes[i].pause()
- }
- }
- for (i = 0, l = this.effects.length; i < l; i++) {
- this.effects[i].pause()
- }
- }
- }
- /**
- * Resumes the effect scope, including all child scopes and effects.
- */
- resume(): void {
- if (this._active) {
- if (this._isPaused) {
- this._isPaused = false
- let i, l
- if (this.scopes) {
- for (i = 0, l = this.scopes.length; i < l; i++) {
- this.scopes[i].resume()
- }
- }
- for (i = 0, l = this.effects.length; i < l; i++) {
- this.effects[i].resume()
- }
- }
- }
- }
- 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.`)
- }
- }
- prevScope: EffectScope | undefined
- /**
- * This should only be called on non-detached scopes
- * @internal
- */
- on(): void {
- if (++this._on === 1) {
- this.prevScope = activeEffectScope
- activeEffectScope = this
- }
- }
- /**
- * This should only be called on non-detached scopes
- * @internal
- */
- off(): void {
- if (this._on > 0 && --this._on === 0) {
- activeEffectScope = this.prevScope
- this.prevScope = undefined
- }
- }
- stop(fromParent?: boolean): void {
- if (this._active) {
- this._active = false
- let i, l
- for (i = 0, l = this.effects.length; i < l; i++) {
- this.effects[i].stop()
- }
- this.effects.length = 0
- for (i = 0, l = this.cleanups.length; i < l; i++) {
- this.cleanups[i]()
- }
- this.cleanups.length = 0
- if (this.scopes) {
- for (i = 0, l = this.scopes.length; i < l; i++) {
- this.scopes[i].stop(true)
- }
- this.scopes.length = 0
- }
- // 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()
- if (last && last !== this) {
- this.parent.scopes![this.index!] = last
- last.index = this.index!
- }
- }
- this.parent = undefined
- }
- }
- }
- /**
- * 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 together. For detailed use cases of this API, please consult its
- * corresponding {@link https://github.com/vuejs/rfcs/blob/master/active-rfcs/0041-reactivity-effect-scope.md | RFC}.
- *
- * @param detached - Can be used to create a "detached" effect scope.
- * @see {@link https://vuejs.org/api/reactivity-advanced.html#effectscope}
- */
- export function effectScope(detached?: boolean): EffectScope {
- return new EffectScope(detached)
- }
- /**
- * Returns the current active effect scope if there is one.
- *
- * @see {@link https://vuejs.org/api/reactivity-advanced.html#getcurrentscope}
- */
- export function getCurrentScope(): EffectScope | undefined {
- return activeEffectScope
- }
- /**
- * Registers a dispose callback on the current active effect scope. The
- * callback will be invoked when the associated effect scope is stopped.
- *
- * @param fn - The callback function to attach to the scope's cleanup.
- * @see {@link https://vuejs.org/api/reactivity-advanced.html#onscopedispose}
- */
- export function onScopeDispose(fn: () => void, failSilently = false): void {
- if (activeEffectScope) {
- activeEffectScope.cleanups.push(fn)
- } else if (__DEV__ && !failSilently) {
- warn(
- `onScopeDispose() is called when there is no active effect scope` +
- ` to be associated with.`,
- )
- }
- }
|