From 6d96fbe005199d9622739b86513b908f1a3b76d7 Mon Sep 17 00:00:00 2001 From: Vida Xie Date: Fri, 19 Sep 2025 22:51:28 +0800 Subject: [PATCH 1/2] feat(effectScope): add lazy-initialized `signal` getter with automatic abort on stop --- .../reactivity/__tests__/effectScope.spec.ts | 17 +++++++++++++++++ packages/reactivity/src/effectScope.ts | 13 +++++++++++++ 2 files changed, 30 insertions(+) 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..4bf109ed40a 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) { @@ -72,6 +82,9 @@ export class EffectScope implements ReactiveNode { if (!this.active) { return } + if (this._controller) { + this._controller.abort() + } this.flags = EffectFlags.STOP let dep = this.deps while (dep !== undefined) { From fe877b95605c4e1ffcc9f3c4ab1bf77caded54af Mon Sep 17 00:00:00 2001 From: Vida Xie Date: Tue, 23 Sep 2025 14:51:54 +0800 Subject: [PATCH 2/2] chore: move abort to last --- packages/reactivity/src/effectScope.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/reactivity/src/effectScope.ts b/packages/reactivity/src/effectScope.ts index 4bf109ed40a..905c52980af 100644 --- a/packages/reactivity/src/effectScope.ts +++ b/packages/reactivity/src/effectScope.ts @@ -82,9 +82,6 @@ export class EffectScope implements ReactiveNode { if (!this.active) { return } - if (this._controller) { - this._controller.abort() - } this.flags = EffectFlags.STOP let dep = this.deps while (dep !== undefined) { @@ -101,6 +98,9 @@ export class EffectScope implements ReactiveNode { unlink(sub) } cleanup(this) + if (this._controller) { + this._controller.abort() + } } }