From: Evan You Date: Fri, 27 Sep 2024 15:10:11 +0000 (+0800) Subject: fix(reactivity): fix nested batch edge case X-Git-Tag: v3.5.10~1 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=93c95dd4cd416503f43a98a1455f62658d22b0b2;p=thirdparty%2Fvuejs%2Fcore.git fix(reactivity): fix nested batch edge case --- diff --git a/packages/reactivity/__tests__/watch.spec.ts b/packages/reactivity/__tests__/watch.spec.ts index f333d7c06e..882e8a245f 100644 --- a/packages/reactivity/__tests__/watch.spec.ts +++ b/packages/reactivity/__tests__/watch.spec.ts @@ -229,4 +229,35 @@ describe('watch', () => { expect(r.value).toBe(1) expect(c.value).toBe(1) }) + + // edge case where a nested endBatch() causes an effect to be batched in a + // nested batch loop with its .next mutated, causing the outer loop to end + // early + test('nested batch edge case', () => { + // useClamp from VueUse + const clamp = (n: number, min: number, max: number) => + Math.min(max, Math.max(min, n)) + function useClamp(src: Ref, min: number, max: number) { + return computed({ + get() { + return (src.value = clamp(src.value, min, max)) + }, + set(val) { + src.value = clamp(val, min, max) + }, + }) + } + + const src = ref(1) + const clamped = useClamp(src, 1, 5) + watch(src, val => (clamped.value = val)) + + const spy = vi.fn() + watch(clamped, spy) + + src.value = 2 + expect(spy).toHaveBeenCalledTimes(1) + src.value = 10 + expect(spy).toHaveBeenCalledTimes(2) + }) }) diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index b32355081e..dad800d146 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -274,6 +274,8 @@ export function endBatch(): void { batchedSub = undefined // 2nd pass: run effects while (e) { + next = e.next + e.next = undefined e.flags &= ~EffectFlags.NOTIFIED if (e.flags & EffectFlags.ACTIVE) { try { @@ -283,8 +285,6 @@ export function endBatch(): void { if (!error) error = err } } - next = e.next - e.next = undefined e = next } }