diff --git a/packages/runtime-core/__tests__/rendererComponent.spec.ts b/packages/runtime-core/__tests__/rendererComponent.spec.ts
index c5faa05de83..fefc4137034 100644
--- a/packages/runtime-core/__tests__/rendererComponent.spec.ts
+++ b/packages/runtime-core/__tests__/rendererComponent.spec.ts
@@ -236,6 +236,105 @@ describe('renderer: component', () => {
expect(serializeInner(root)).toBe(`
1
1
`)
})
+ test('child only updates once when triggered in multiple ways', async () => {
+ const a = ref(0)
+ const calls: string[] = []
+
+ const Parent = {
+ setup() {
+ return () => {
+ calls.push('render parent')
+ return h(Child, { count: a.value }, () => a.value)
+ }
+ },
+ }
+
+ const Child = {
+ props: ['count'],
+ setup(props: any) {
+ return () => {
+ calls.push('render child')
+ return `${props.count} - ${a.value}`
+ }
+ },
+ }
+
+ render(h(Parent), nodeOps.createElement('div'))
+ expect(calls).toEqual(['render parent', 'render child'])
+
+ // This will trigger child rendering directly, as well as via a prop change
+ a.value++
+ await nextTick()
+ expect(calls).toEqual([
+ 'render parent',
+ 'render child',
+ 'render parent',
+ 'render child',
+ ])
+ })
+
+ // #7745
+ test(`an earlier update doesn't lead to excessive subsequent updates`, async () => {
+ const globalCount = ref(0)
+ const parentCount = ref(0)
+ const calls: string[] = []
+
+ const Root = {
+ setup() {
+ return () => {
+ calls.push('render root')
+ return h(Parent, { count: globalCount.value })
+ }
+ },
+ }
+
+ const Parent = {
+ props: ['count'],
+ setup(props: any) {
+ return () => {
+ calls.push('render parent')
+ return [
+ `${globalCount.value} - ${props.count}`,
+ h(Child, { count: parentCount.value }),
+ ]
+ }
+ },
+ }
+
+ const Child = {
+ props: ['count'],
+ setup(props: any) {
+ watch(
+ () => props.count,
+ () => {
+ calls.push('child watcher')
+ globalCount.value = props.count
+ },
+ )
+
+ return () => {
+ calls.push('render child')
+ }
+ },
+ }
+
+ render(h(Root), nodeOps.createElement('div'))
+ expect(calls).toEqual(['render root', 'render parent', 'render child'])
+
+ parentCount.value++
+ await nextTick()
+ expect(calls).toEqual([
+ 'render root',
+ 'render parent',
+ 'render child',
+ 'render parent',
+ 'child watcher',
+ 'render child',
+ 'render root',
+ 'render parent',
+ ])
+ })
+
// #2521
test('should pause tracking deps when initializing legacy options', async () => {
let childInstance = null as any
diff --git a/packages/runtime-core/__tests__/scheduler.spec.ts b/packages/runtime-core/__tests__/scheduler.spec.ts
index 8d74330da44..5c5b04673ab 100644
--- a/packages/runtime-core/__tests__/scheduler.spec.ts
+++ b/packages/runtime-core/__tests__/scheduler.spec.ts
@@ -3,7 +3,6 @@ import {
SchedulerJobFlags,
flushPostFlushCbs,
flushPreFlushCbs,
- invalidateJob,
nextTick,
queueJob,
queuePostFlushCb,
@@ -444,33 +443,6 @@ describe('scheduler', () => {
})
})
- test('invalidateJob', async () => {
- const calls: string[] = []
- const job1 = () => {
- calls.push('job1')
- invalidateJob(job2)
- job2()
- }
- const job2 = () => {
- calls.push('job2')
- }
- const job3 = () => {
- calls.push('job3')
- }
- const job4 = () => {
- calls.push('job4')
- }
- // queue all jobs
- queueJob(job1)
- queueJob(job2)
- queueJob(job3)
- queuePostFlushCb(job4)
- expect(calls).toEqual([])
- await nextTick()
- // job2 should be called only once
- expect(calls).toEqual(['job1', 'job2', 'job3', 'job4'])
- })
-
test('sort job based on id', async () => {
const calls: string[] = []
const job1 = () => calls.push('job1')
diff --git a/packages/runtime-core/src/renderer.ts b/packages/runtime-core/src/renderer.ts
index 3d1cc6849c7..ce063989502 100644
--- a/packages/runtime-core/src/renderer.ts
+++ b/packages/runtime-core/src/renderer.ts
@@ -45,7 +45,6 @@ import {
type SchedulerJobs,
flushPostFlushCbs,
flushPreFlushCbs,
- invalidateJob,
queueJob,
queuePostFlushCb,
} from './scheduler'
@@ -1255,9 +1254,6 @@ function baseCreateRenderer(
} 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.
instance.update()
}
diff --git a/packages/runtime-core/src/scheduler.ts b/packages/runtime-core/src/scheduler.ts
index 256ab202780..aa12b6896a7 100644
--- a/packages/runtime-core/src/scheduler.ts
+++ b/packages/runtime-core/src/scheduler.ts
@@ -122,13 +122,6 @@ function queueFlush() {
}
}
-export function invalidateJob(job: SchedulerJob): void {
- const i = queue.indexOf(job)
- if (i > flushIndex) {
- queue.splice(i, 1)
- }
-}
-
export function queuePostFlushCb(cb: SchedulerJobs): void {
if (!isArray(cb)) {
if (activePostFlushCbs && cb.id === -1) {