Quellcode durchsuchen

fix(reactivity): prevent overwriting `next` property during batch processing (#12075)

close #12072
Tycho vor 1 Jahr
Ursprung
Commit
d3f5e6e531

+ 17 - 0
packages/reactivity/__tests__/watch.spec.ts

@@ -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])
+  })
 })

+ 1 - 1
packages/reactivity/src/computed.ts

@@ -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

+ 19 - 14
packages/reactivity/src/effect.ts

@@ -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) {