From 8a87074df013fdbb0e88f34074c2605e4af2937c Mon Sep 17 00:00:00 2001 From: Evan You Date: Mon, 10 Feb 2020 13:09:15 -0500 Subject: [PATCH] fix(runtime-core/scheduler): avoid duplicate updates of child component --- .../runtime-core/__tests__/scheduler.spec.ts | 26 ++++++++++++++++++- packages/runtime-core/src/renderer.ts | 10 ++++++- packages/runtime-core/src/scheduler.ts | 12 ++++++++- 3 files changed, 45 insertions(+), 3 deletions(-) diff --git a/packages/runtime-core/__tests__/scheduler.spec.ts b/packages/runtime-core/__tests__/scheduler.spec.ts index 03a7abc1629..65535d679a0 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 cee334a8fcc..e2f87c8f277 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 78d3d59cd1d..a13a01280e0 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) }