diff --git a/packages/reactivity/__tests__/effectScope.spec.ts b/packages/reactivity/__tests__/effectScope.spec.ts index 93ee648e2df..08b4ac76652 100644 --- a/packages/reactivity/__tests__/effectScope.spec.ts +++ b/packages/reactivity/__tests__/effectScope.spec.ts @@ -363,6 +363,23 @@ describe('reactivity/effect/scope', () => { expect(getEffectsCount(scope)).toBe(0) expect(scope.cleanupsLength).toBe(0) }) + + test('signal', () => { + const scope = effectScope() + // should not create an `AbortController` until `scope.signal` is accessed + expect((scope as any)._controller).toBeUndefined() + + const { signal } = scope + expect((scope as any)._controller).toBeDefined() + expect(signal).toBeDefined() + + const spy = vi.fn() + signal.addEventListener('abort', spy) + + scope.stop() + // should trigger `abort` on the `signal` when `scope.stop()` is called. + expect(spy).toBeCalled() + }) }) function getEffectsCount(scope: EffectScope): number { diff --git a/packages/reactivity/src/effectScope.ts b/packages/reactivity/src/effectScope.ts index 36c9b85e8d7..905c52980af 100644 --- a/packages/reactivity/src/effectScope.ts +++ b/packages/reactivity/src/effectScope.ts @@ -19,6 +19,16 @@ export class EffectScope implements ReactiveNode { * @internal */ cleanupsLength = 0 + /** + * @internal + */ + private _controller: AbortController | undefined + + get signal(): AbortSignal { + if (!this._controller) this._controller = new AbortController() + + return this._controller.signal + } constructor(detached = false) { if (!detached && activeEffectScope) { @@ -88,6 +98,9 @@ export class EffectScope implements ReactiveNode { unlink(sub) } cleanup(this) + if (this._controller) { + this._controller.abort() + } } }