From 154233abdb19b8330bbc1ff0d3e007f2558cd81c Mon Sep 17 00:00:00 2001 From: Evan You Date: Wed, 13 Apr 2022 17:00:31 +0800 Subject: [PATCH] fix(reactivity): fix ref tracking of self-stopping effects close #5707 --- packages/reactivity/__tests__/effect.spec.ts | 21 ++++++++++++++++++++ packages/reactivity/src/effect.ts | 13 +++++++++++- 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/packages/reactivity/__tests__/effect.spec.ts b/packages/reactivity/__tests__/effect.spec.ts index 77d069fc618..34b9c575c93 100644 --- a/packages/reactivity/__tests__/effect.spec.ts +++ b/packages/reactivity/__tests__/effect.spec.ts @@ -1,4 +1,5 @@ import { + ref, reactive, effect, stop, @@ -801,6 +802,26 @@ describe('reactivity/effect', () => { expect(dummy).toBe(3) }) + // #5707 + // when an effect completes its run, it should clear the tracking bits of + // its tracked deps. However, if the effect stops itself, the deps list is + // emptied so their bits are never cleared. + it('edge case: self-stopping effect tracking ref', () => { + const c = ref(true) + const runner = effect(() => { + // reference ref + if (!c.value) { + // stop itself while running + stop(runner) + } + }) + // trigger run + c.value = !c.value + // should clear bits + expect((c as any).dep.w).toBe(0) + expect((c as any).dep.n).toBe(0) + }) + it('events: onStop', () => { const onStop = jest.fn() const runner = effect(() => {}, { diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index b69b238975e..77312eaa2cf 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -64,6 +64,10 @@ export class ReactiveEffect { * @internal */ allowRecurse?: boolean + /** + * @internal + */ + private deferStop?: boolean onStop?: () => void // dev only @@ -114,11 +118,18 @@ export class ReactiveEffect { activeEffect = this.parent shouldTrack = lastShouldTrack this.parent = undefined + + if (this.deferStop) { + this.stop() + } } } stop() { - if (this.active) { + // stopped while running itself - defer the cleanup + if (activeEffect === this) { + this.deferStop = true + } else if (this.active) { cleanupEffect(this) if (this.onStop) { this.onStop()