Skip to content

Commit 611437a

Browse files
committed
fix(runtime-core/scheduler): allow component render functions to trigger itself
fix #1801
1 parent bc6f252 commit 611437a

File tree

4 files changed

+24
-12
lines changed

4 files changed

+24
-12
lines changed

packages/runtime-core/__tests__/scheduler.spec.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -451,7 +451,7 @@ describe('scheduler', () => {
451451
expect(count).toBe(1)
452452
})
453453

454-
test('should allow watcher callbacks to trigger itself', async () => {
454+
test('should allow explicitly marked jobs to trigger itself', async () => {
455455
// normal job
456456
let count = 0
457457
const job = () => {
@@ -460,7 +460,7 @@ describe('scheduler', () => {
460460
queueJob(job)
461461
}
462462
}
463-
job.cb = true
463+
job.allowRecurse = true
464464
queueJob(job)
465465
await nextTick()
466466
expect(count).toBe(3)
@@ -472,7 +472,7 @@ describe('scheduler', () => {
472472
queuePostFlushCb(cb)
473473
}
474474
}
475-
cb.cb = true
475+
cb.allowRecurse = true
476476
queuePostFlushCb(cb)
477477
await nextTick()
478478
expect(count).toBe(5)

packages/runtime-core/src/apiWatch.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,7 @@ function doWatch(
261261

262262
// important: mark the job as a watcher callback so that scheduler knows it
263263
// it is allowed to self-trigger (#1727)
264-
job.cb = !!cb
264+
job.allowRecurse = !!cb
265265

266266
let scheduler: (job: () => any) => void
267267
if (flush === 'sync') {

packages/runtime-core/src/renderer.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@ import {
4141
queuePostFlushCb,
4242
flushPostFlushCbs,
4343
invalidateJob,
44-
flushPreFlushCbs
44+
flushPreFlushCbs,
45+
SchedulerJob
4546
} from './scheduler'
4647
import { effect, stop, ReactiveEffectOptions, isRef } from '@vue/reactivity'
4748
import { updateProps } from './componentProps'
@@ -1429,6 +1430,8 @@ function baseCreateRenderer(
14291430
}
14301431
}
14311432
}, __DEV__ ? createDevEffectOptions(instance) : prodEffectOptions)
1433+
// #1801 mark it to allow recursive updates
1434+
;(instance.update as SchedulerJob).allowRecurse = true
14321435
}
14331436

14341437
const updateComponentPreRender = (

packages/runtime-core/src/scheduler.ts

+16-7
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,18 @@ export interface SchedulerJob {
88
*/
99
id?: number
1010
/**
11-
* Indicates this is a watch() callback and is allowed to trigger itself.
12-
* A watch callback doesn't track its dependencies so if it triggers itself
13-
* again, it's likely intentional and it is the user's responsibility to
14-
* perform recursive state mutation that eventually stabilizes.
11+
* Indicates whether the job is allowed to recursively trigger itself.
12+
* By default, a job cannot trigger itself because some built-in method calls,
13+
* e.g. Array.prototype.push actually performs reads as well (#1740) which
14+
* can lead to confusing infinite loops.
15+
* The allowed cases are component render functions and watch callbacks.
16+
* Render functions may update child component props, which in turn trigger
17+
* flush: "pre" watch callbacks that mutates state that the parent relies on
18+
* (#1801). Watch callbacks doesn't track its dependencies so if it triggers
19+
* itself again, it's likely intentional and it is the user's responsibility
20+
* to perform recursive state mutation that eventually stabilizes (#1727).
1521
*/
16-
cb?: boolean
22+
allowRecurse?: boolean
1723
}
1824

1925
let isFlushing = false
@@ -54,7 +60,7 @@ export function queueJob(job: SchedulerJob) {
5460
(!queue.length ||
5561
!queue.includes(
5662
job,
57-
isFlushing && job.cb ? flushIndex + 1 : flushIndex
63+
isFlushing && job.allowRecurse ? flushIndex + 1 : flushIndex
5864
)) &&
5965
job !== currentPreFlushParentJob
6066
) {
@@ -86,7 +92,10 @@ function queueCb(
8692
if (!isArray(cb)) {
8793
if (
8894
!activeQueue ||
89-
!activeQueue.includes(cb, (cb as SchedulerJob).cb ? index + 1 : index)
95+
!activeQueue.includes(
96+
cb,
97+
(cb as SchedulerJob).allowRecurse ? index + 1 : index
98+
)
9099
) {
91100
pendingQueue.push(cb)
92101
}

0 commit comments

Comments
 (0)