Просмотр исходного кода

fix(watch): fix queueing multiple post watchers

fix #12664
Evan You 3 лет назад
Родитель
Сommit
25ffdb62d2
4 измененных файлов с 46 добавлено и 2 удалено
  1. 10 1
      src/core/observer/scheduler.ts
  2. 2 0
      src/core/observer/watcher.ts
  3. 1 1
      src/v3/apiWatch.ts
  4. 33 0
      test/unit/features/v3/apiWatch.spec.ts

+ 10 - 1
src/core/observer/scheduler.ts

@@ -59,6 +59,15 @@ if (inBrowser && !isIE) {
   }
   }
 }
 }
 
 
+const sortCompareFn = (a: Watcher, b: Watcher): number => {
+  if (a.post) {
+    if (!b.post) return 1
+  } else if (b.post) {
+    return -1
+  }
+  return a.id - b.id
+}
+
 /**
 /**
  * Flush both queues and run the watchers.
  * Flush both queues and run the watchers.
  */
  */
@@ -75,7 +84,7 @@ function flushSchedulerQueue() {
   //    user watchers are created before the render watcher)
   //    user watchers are created before the render watcher)
   // 3. If a component is destroyed during a parent component's watcher run,
   // 3. If a component is destroyed during a parent component's watcher run,
   //    its watchers can be skipped.
   //    its watchers can be skipped.
-  queue.sort((a, b) => a.id - b.id)
+  queue.sort(sortCompareFn)
 
 
   // do not cache length because more watchers might be pushed
   // do not cache length because more watchers might be pushed
   // as we run existing watchers
   // as we run existing watchers

+ 2 - 0
src/core/observer/watcher.ts

@@ -58,6 +58,7 @@ export default class Watcher implements DepTarget {
   noRecurse?: boolean
   noRecurse?: boolean
   getter: Function
   getter: Function
   value: any
   value: any
+  post: boolean
 
 
   // dev only
   // dev only
   onTrack?: ((event: DebuggerEvent) => void) | undefined
   onTrack?: ((event: DebuggerEvent) => void) | undefined
@@ -93,6 +94,7 @@ export default class Watcher implements DepTarget {
     this.cb = cb
     this.cb = cb
     this.id = ++uid // uid for batching
     this.id = ++uid // uid for batching
     this.active = true
     this.active = true
+    this.post = false
     this.dirty = this.lazy // for lazy watchers
     this.dirty = this.lazy // for lazy watchers
     this.deps = []
     this.deps = []
     this.newDeps = []
     this.newDeps = []

+ 1 - 1
src/v3/apiWatch.ts

@@ -313,7 +313,7 @@ function doWatch(
   if (flush === 'sync') {
   if (flush === 'sync') {
     watcher.update = watcher.run
     watcher.update = watcher.run
   } else if (flush === 'post') {
   } else if (flush === 'post') {
-    watcher.id = Infinity
+    watcher.post = true
     watcher.update = () => queueWatcher(watcher)
     watcher.update = () => queueWatcher(watcher)
   } else {
   } else {
     // pre
     // pre

+ 33 - 0
test/unit/features/v3/apiWatch.spec.ts

@@ -1167,4 +1167,37 @@ describe('api: watch', () => {
     set(r.value, 'foo', 1)
     set(r.value, 'foo', 1)
     expect(spy).not.toHaveBeenCalled()
     expect(spy).not.toHaveBeenCalled()
   })
   })
+
+  // #12664
+  it('queueing multiple flush: post watchers', async () => {
+    const parentSpy = vi.fn()
+    const childSpy = vi.fn()
+
+    const Child = {
+      setup() {
+        const el = ref()
+        watch(el, childSpy, { flush: 'post' })
+        return { el }
+      },
+      template: `<div><span ref="el">hello child</span></div>`
+    }
+    const App = {
+      components: { Child },
+      setup() {
+        const el = ref()
+        watch(el, parentSpy, { flush: 'post' })
+        return { el }
+      },
+      template: `<div><Child /><span ref="el">hello app1</span></div>`
+    }
+
+    const container = document.createElement('div')
+    const root = document.createElement('div')
+    container.appendChild(root)
+    new Vue(App).$mount(root)
+
+    await nextTick()
+    expect(parentSpy).toHaveBeenCalledTimes(1)
+    expect(childSpy).toHaveBeenCalledTimes(1)
+  })
 })
 })