fix(runtime-core/scheduler): allow component render functions to trigger itself
fix #1801
This commit is contained in:
parent
bc6f252c4a
commit
611437a3fe
@ -451,7 +451,7 @@ describe('scheduler', () => {
|
||||
expect(count).toBe(1)
|
||||
})
|
||||
|
||||
test('should allow watcher callbacks to trigger itself', async () => {
|
||||
test('should allow explicitly marked jobs to trigger itself', async () => {
|
||||
// normal job
|
||||
let count = 0
|
||||
const job = () => {
|
||||
@ -460,7 +460,7 @@ describe('scheduler', () => {
|
||||
queueJob(job)
|
||||
}
|
||||
}
|
||||
job.cb = true
|
||||
job.allowRecurse = true
|
||||
queueJob(job)
|
||||
await nextTick()
|
||||
expect(count).toBe(3)
|
||||
@ -472,7 +472,7 @@ describe('scheduler', () => {
|
||||
queuePostFlushCb(cb)
|
||||
}
|
||||
}
|
||||
cb.cb = true
|
||||
cb.allowRecurse = true
|
||||
queuePostFlushCb(cb)
|
||||
await nextTick()
|
||||
expect(count).toBe(5)
|
||||
|
@ -261,7 +261,7 @@ function doWatch(
|
||||
|
||||
// important: mark the job as a watcher callback so that scheduler knows it
|
||||
// it is allowed to self-trigger (#1727)
|
||||
job.cb = !!cb
|
||||
job.allowRecurse = !!cb
|
||||
|
||||
let scheduler: (job: () => any) => void
|
||||
if (flush === 'sync') {
|
||||
|
@ -41,7 +41,8 @@ import {
|
||||
queuePostFlushCb,
|
||||
flushPostFlushCbs,
|
||||
invalidateJob,
|
||||
flushPreFlushCbs
|
||||
flushPreFlushCbs,
|
||||
SchedulerJob
|
||||
} from './scheduler'
|
||||
import { effect, stop, ReactiveEffectOptions, isRef } from '@vue/reactivity'
|
||||
import { updateProps } from './componentProps'
|
||||
@ -1429,6 +1430,8 @@ function baseCreateRenderer(
|
||||
}
|
||||
}
|
||||
}, __DEV__ ? createDevEffectOptions(instance) : prodEffectOptions)
|
||||
// #1801 mark it to allow recursive updates
|
||||
;(instance.update as SchedulerJob).allowRecurse = true
|
||||
}
|
||||
|
||||
const updateComponentPreRender = (
|
||||
|
@ -8,12 +8,18 @@ export interface SchedulerJob {
|
||||
*/
|
||||
id?: number
|
||||
/**
|
||||
* Indicates this is a watch() callback and is allowed to trigger itself.
|
||||
* A watch callback doesn't track its dependencies so if it triggers itself
|
||||
* again, it's likely intentional and it is the user's responsibility to
|
||||
* perform recursive state mutation that eventually stabilizes.
|
||||
* Indicates whether the job is allowed to recursively trigger itself.
|
||||
* By default, a job cannot trigger itself because some built-in method calls,
|
||||
* e.g. Array.prototype.push actually performs reads as well (#1740) which
|
||||
* can lead to confusing infinite loops.
|
||||
* The allowed cases are component render functions and watch callbacks.
|
||||
* Render functions may update child component props, which in turn trigger
|
||||
* flush: "pre" watch callbacks that mutates state that the parent relies on
|
||||
* (#1801). Watch callbacks doesn't track its dependencies so if it triggers
|
||||
* itself again, it's likely intentional and it is the user's responsibility
|
||||
* to perform recursive state mutation that eventually stabilizes (#1727).
|
||||
*/
|
||||
cb?: boolean
|
||||
allowRecurse?: boolean
|
||||
}
|
||||
|
||||
let isFlushing = false
|
||||
@ -54,7 +60,7 @@ export function queueJob(job: SchedulerJob) {
|
||||
(!queue.length ||
|
||||
!queue.includes(
|
||||
job,
|
||||
isFlushing && job.cb ? flushIndex + 1 : flushIndex
|
||||
isFlushing && job.allowRecurse ? flushIndex + 1 : flushIndex
|
||||
)) &&
|
||||
job !== currentPreFlushParentJob
|
||||
) {
|
||||
@ -86,7 +92,10 @@ function queueCb(
|
||||
if (!isArray(cb)) {
|
||||
if (
|
||||
!activeQueue ||
|
||||
!activeQueue.includes(cb, (cb as SchedulerJob).cb ? index + 1 : index)
|
||||
!activeQueue.includes(
|
||||
cb,
|
||||
(cb as SchedulerJob).allowRecurse ? index + 1 : index
|
||||
)
|
||||
) {
|
||||
pendingQueue.push(cb)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user