]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(watch): should not fire pre watcher on child component unmount (#7181)
authorRudy <xuhaodong66@foxmail.com>
Fri, 8 Dec 2023 04:24:44 +0000 (12:24 +0800)
committerGitHub <noreply@github.com>
Fri, 8 Dec 2023 04:24:44 +0000 (12:24 +0800)
close #7030

packages/runtime-core/__tests__/apiWatch.spec.ts
packages/runtime-core/src/renderer.ts
packages/runtime-core/src/scheduler.ts

index 48fdd2888ec02df65bddca2e9513925a8bf2d0e2..2cb676aa8dcb0b801410b85dadb943fe357f3304 100644 (file)
@@ -549,6 +549,98 @@ describe('api: watch', () => {
     expect(cb).not.toHaveBeenCalled()
   })
 
+  // #7030
+  it('should not fire on child component unmount w/ flush: pre', async () => {
+    const visible = ref(true)
+    const cb = vi.fn()
+    const Parent = defineComponent({
+      props: ['visible'],
+      render() {
+        return visible.value ? h(Comp) : null
+      }
+    })
+    const Comp = {
+      setup() {
+        watch(visible, cb, { flush: 'pre' })
+      },
+      render() {}
+    }
+    const App = {
+      render() {
+        return h(Parent, {
+          visible: visible.value
+        })
+      }
+    }
+    render(h(App), nodeOps.createElement('div'))
+    expect(cb).not.toHaveBeenCalled()
+    visible.value = false
+    await nextTick()
+    expect(cb).not.toHaveBeenCalled()
+  })
+
+  // #7030
+  it('flush: pre watcher in child component should not fire before parent update', async () => {
+    const b = ref(0)
+    const calls: string[] = []
+
+    const Comp = {
+      setup() {
+        watch(
+          () => b.value,
+          val => {
+            calls.push('watcher child')
+          },
+          { flush: 'pre' }
+        )
+        return () => {
+          b.value
+          calls.push('render child')
+        }
+      }
+    }
+
+    const Parent = {
+      props: ['a'],
+      setup() {
+        watch(
+          () => b.value,
+          val => {
+            calls.push('watcher parent')
+          },
+          { flush: 'pre' }
+        )
+        return () => {
+          b.value
+          calls.push('render parent')
+          return h(Comp)
+        }
+      }
+    }
+
+    const App = {
+      render() {
+        return h(Parent, {
+          a: b.value
+        })
+      }
+    }
+
+    render(h(App), nodeOps.createElement('div'))
+    expect(calls).toEqual(['render parent', 'render child'])
+
+    b.value++
+    await nextTick()
+    expect(calls).toEqual([
+      'render parent',
+      'render child',
+      'watcher parent',
+      'render parent',
+      'watcher child',
+      'render child'
+    ])
+  })
+
   // #1763
   it('flush: pre watcher watching props should fire before child update', async () => {
     const a = ref(0)
index 8a3b4ffa3af53882a221cb4a502a239aea117c4d..2f31ad9a0442331b34b1876c3794c915caab0ed6 100644 (file)
@@ -1582,7 +1582,7 @@ function baseCreateRenderer(
     pauseTracking()
     // props update may have triggered pre-flush watchers.
     // flush them before the render update.
-    flushPreFlushCbs()
+    flushPreFlushCbs(instance)
     resetTracking()
   }
 
index e4c7fbc0f4d0c54a2a1f7435003f4cd59a6db364..5b096b563e4a52373b59e39f085a19a423e62208 100644 (file)
@@ -139,6 +139,7 @@ export function queuePostFlushCb(cb: SchedulerJobs) {
 }
 
 export function flushPreFlushCbs(
+  instance?: ComponentInternalInstance,
   seen?: CountMap,
   // if currently flushing, skip the current job itself
   i = isFlushing ? flushIndex + 1 : 0
@@ -149,6 +150,9 @@ export function flushPreFlushCbs(
   for (; i < queue.length; i++) {
     const cb = queue[i]
     if (cb && cb.pre) {
+      if (instance && cb.id !== instance.uid) {
+        continue
+      }
       if (__DEV__ && checkRecursiveUpdates(seen!, cb)) {
         continue
       }