diff --git a/packages/runtime-core/__tests__/scheduler.spec.ts b/packages/runtime-core/__tests__/scheduler.spec.ts index 03a7abc1..65535d67 100644 --- a/packages/runtime-core/__tests__/scheduler.spec.ts +++ b/packages/runtime-core/__tests__/scheduler.spec.ts @@ -1,4 +1,9 @@ -import { queueJob, nextTick, queuePostFlushCb } from '../src/scheduler' +import { + queueJob, + nextTick, + queuePostFlushCb, + invalidateJob +} from '../src/scheduler' describe('scheduler', () => { it('nextTick', async () => { @@ -230,4 +235,23 @@ describe('scheduler', () => { expect(calls).toEqual(['job1', 'job2', 'cb1', 'cb2']) }) }) + + test('invalidateJob', async () => { + const calls: string[] = [] + const job1 = () => { + calls.push('job1') + invalidateJob(job2) + job2() + } + const job2 = () => { + calls.push('job2') + } + // queue both jobs + queueJob(job1) + queueJob(job2) + expect(calls).toEqual([]) + await nextTick() + // job2 should be called only once + expect(calls).toEqual(['job1', 'job2']) + }) }) diff --git a/packages/runtime-core/src/renderer.ts b/packages/runtime-core/src/renderer.ts index cee334a8..e2f87c8f 100644 --- a/packages/runtime-core/src/renderer.ts +++ b/packages/runtime-core/src/renderer.ts @@ -30,7 +30,12 @@ import { isFunction, PatchFlags } from '@vue/shared' -import { queueJob, queuePostFlushCb, flushPostFlushCbs } from './scheduler' +import { + queueJob, + queuePostFlushCb, + flushPostFlushCbs, + invalidateJob +} from './scheduler' import { effect, stop, @@ -895,6 +900,9 @@ export function createRenderer< } else { // normal update instance.next = n2 + // in case the child component is also queued, remove it to avoid + // double updating the same child component in the same flush. + invalidateJob(instance.update) // instance.update is the reactive effect runner. instance.update() } diff --git a/packages/runtime-core/src/scheduler.ts b/packages/runtime-core/src/scheduler.ts index 78d3d59c..a13a0128 100644 --- a/packages/runtime-core/src/scheduler.ts +++ b/packages/runtime-core/src/scheduler.ts @@ -1,7 +1,7 @@ import { ErrorCodes, callWithErrorHandling } from './errorHandling' import { isArray } from '@vue/shared' -const queue: Function[] = [] +const queue: (Function | null)[] = [] const postFlushCbs: Function[] = [] const p = Promise.resolve() @@ -22,6 +22,13 @@ export function queueJob(job: () => void) { } } +export function invalidateJob(job: () => void) { + const i = queue.indexOf(job) + if (i > -1) { + queue[i] = null + } +} + export function queuePostFlushCb(cb: Function | Function[]) { if (!isArray(cb)) { postFlushCbs.push(cb) @@ -64,6 +71,9 @@ function flushJobs(seen?: CountMap) { seen = seen || new Map() } while ((job = queue.shift())) { + if (job === null) { + continue + } if (__DEV__) { checkRecursiveUpdates(seen!, job) }