]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(reactivity): prevent overwriting `next` property during batch processing (#12075)
authorTycho <jh.leong@outlook.com>
Thu, 3 Oct 2024 15:16:52 +0000 (23:16 +0800)
committerGitHub <noreply@github.com>
Thu, 3 Oct 2024 15:16:52 +0000 (23:16 +0800)
close #12072

packages/reactivity/__tests__/watch.spec.ts
packages/reactivity/src/computed.ts
packages/reactivity/src/effect.ts

index 882e8a245faca290b237ba6bc36203caf9db43bc..245acfd63be844be594e56d4ea76875a19334827 100644 (file)
@@ -260,4 +260,21 @@ describe('watch', () => {
     src.value = 10
     expect(spy).toHaveBeenCalledTimes(2)
   })
+
+  test('should ensure correct execution order in batch processing', () => {
+    const dummy: number[] = []
+    const n1 = ref(0)
+    const n2 = ref(0)
+    const sum = computed(() => n1.value + n2.value)
+    watch(n1, () => {
+      dummy.push(1)
+      n2.value++
+    })
+    watch(sum, () => dummy.push(2))
+    watch(n1, () => dummy.push(3))
+
+    n1.value++
+
+    expect(dummy).toEqual([1, 2, 3])
+  })
 })
index 628cf46367b3e22715a864410ad23c74ddf75120..ea798e201d4bfc7d4d44df09143586883b7c6f8d 100644 (file)
@@ -121,7 +121,7 @@ export class ComputedRefImpl<T = any> implements Subscriber {
       // avoid infinite self recursion
       activeSub !== this
     ) {
-      batch(this)
+      batch(this, true)
       return true
     } else if (__DEV__) {
       // TODO warn
index dad800d146de1449a749f86d0c0a6da66fcb9182..243518839b301fef52a6793cf924911da6603926 100644 (file)
@@ -234,9 +234,15 @@ export class ReactiveEffect<T = any>
 
 let batchDepth = 0
 let batchedSub: Subscriber | undefined
+let batchedComputed: Subscriber | undefined
 
-export function batch(sub: Subscriber): void {
+export function batch(sub: Subscriber, isComputed = false): void {
   sub.flags |= EffectFlags.NOTIFIED
+  if (isComputed) {
+    sub.next = batchedComputed
+    batchedComputed = sub
+    return
+  }
   sub.next = batchedSub
   batchedSub = sub
 }
@@ -257,24 +263,23 @@ export function endBatch(): void {
     return
   }
 
+  if (batchedComputed) {
+    let e: Subscriber | undefined = batchedComputed
+    batchedComputed = undefined
+    while (e) {
+      const next: Subscriber | undefined = e.next
+      e.next = undefined
+      e.flags &= ~EffectFlags.NOTIFIED
+      e = next
+    }
+  }
+
   let error: unknown
   while (batchedSub) {
     let e: Subscriber | undefined = batchedSub
-    let next: Subscriber | undefined
-    // 1st pass: clear notified flags for computed upfront
-    // we use the ACTIVE flag as a discriminator between computed and effect,
-    // since NOTIFIED is useless for an inactive effect anyway.
-    while (e) {
-      if (!(e.flags & EffectFlags.ACTIVE)) {
-        e.flags &= ~EffectFlags.NOTIFIED
-      }
-      e = e.next
-    }
-    e = batchedSub
     batchedSub = undefined
-    // 2nd pass: run effects
     while (e) {
-      next = e.next
+      const next: Subscriber | undefined = e.next
       e.next = undefined
       e.flags &= ~EffectFlags.NOTIFIED
       if (e.flags & EffectFlags.ACTIVE) {