diff --git a/src/core/observer/scheduler.ts b/src/core/observer/scheduler.ts index 24e711c36fd..b019d9f6873 100644 --- a/src/core/observer/scheduler.ts +++ b/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. */ @@ -75,7 +84,7 @@ function flushSchedulerQueue() { // user watchers are created before the render watcher) // 3. If a component is destroyed during a parent component's watcher run, // 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 // as we run existing watchers diff --git a/src/core/observer/watcher.ts b/src/core/observer/watcher.ts index f07b0fd379c..b1f491acb33 100644 --- a/src/core/observer/watcher.ts +++ b/src/core/observer/watcher.ts @@ -58,6 +58,7 @@ export default class Watcher implements DepTarget { noRecurse?: boolean getter: Function value: any + post: boolean // dev only onTrack?: ((event: DebuggerEvent) => void) | undefined @@ -93,6 +94,7 @@ export default class Watcher implements DepTarget { this.cb = cb this.id = ++uid // uid for batching this.active = true + this.post = false this.dirty = this.lazy // for lazy watchers this.deps = [] this.newDeps = [] diff --git a/src/v3/apiWatch.ts b/src/v3/apiWatch.ts index 6a0b65fa298..7fe5eeb9dcc 100644 --- a/src/v3/apiWatch.ts +++ b/src/v3/apiWatch.ts @@ -313,7 +313,7 @@ function doWatch( if (flush === 'sync') { watcher.update = watcher.run } else if (flush === 'post') { - watcher.id = Infinity + watcher.post = true watcher.update = () => queueWatcher(watcher) } else { // pre diff --git a/test/unit/features/v3/apiWatch.spec.ts b/test/unit/features/v3/apiWatch.spec.ts index c1b301ae9e8..0206e8df3bf 100644 --- a/test/unit/features/v3/apiWatch.spec.ts +++ b/test/unit/features/v3/apiWatch.spec.ts @@ -1167,4 +1167,37 @@ describe('api: watch', () => { set(r.value, 'foo', 1) 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: `
hello child
` + } + const App = { + components: { Child }, + setup() { + const el = ref() + watch(el, parentSpy, { flush: 'post' }) + return { el } + }, + template: `
hello app1
` + } + + 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) + }) })