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)
|
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
|
// normal job
|
||||||
let count = 0
|
let count = 0
|
||||||
const job = () => {
|
const job = () => {
|
||||||
@ -460,7 +460,7 @@ describe('scheduler', () => {
|
|||||||
queueJob(job)
|
queueJob(job)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
job.cb = true
|
job.allowRecurse = true
|
||||||
queueJob(job)
|
queueJob(job)
|
||||||
await nextTick()
|
await nextTick()
|
||||||
expect(count).toBe(3)
|
expect(count).toBe(3)
|
||||||
@ -472,7 +472,7 @@ describe('scheduler', () => {
|
|||||||
queuePostFlushCb(cb)
|
queuePostFlushCb(cb)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cb.cb = true
|
cb.allowRecurse = true
|
||||||
queuePostFlushCb(cb)
|
queuePostFlushCb(cb)
|
||||||
await nextTick()
|
await nextTick()
|
||||||
expect(count).toBe(5)
|
expect(count).toBe(5)
|
||||||
|
@ -261,7 +261,7 @@ function doWatch(
|
|||||||
|
|
||||||
// important: mark the job as a watcher callback so that scheduler knows it
|
// important: mark the job as a watcher callback so that scheduler knows it
|
||||||
// it is allowed to self-trigger (#1727)
|
// it is allowed to self-trigger (#1727)
|
||||||
job.cb = !!cb
|
job.allowRecurse = !!cb
|
||||||
|
|
||||||
let scheduler: (job: () => any) => void
|
let scheduler: (job: () => any) => void
|
||||||
if (flush === 'sync') {
|
if (flush === 'sync') {
|
||||||
|
@ -41,7 +41,8 @@ import {
|
|||||||
queuePostFlushCb,
|
queuePostFlushCb,
|
||||||
flushPostFlushCbs,
|
flushPostFlushCbs,
|
||||||
invalidateJob,
|
invalidateJob,
|
||||||
flushPreFlushCbs
|
flushPreFlushCbs,
|
||||||
|
SchedulerJob
|
||||||
} from './scheduler'
|
} from './scheduler'
|
||||||
import { effect, stop, ReactiveEffectOptions, isRef } from '@vue/reactivity'
|
import { effect, stop, ReactiveEffectOptions, isRef } from '@vue/reactivity'
|
||||||
import { updateProps } from './componentProps'
|
import { updateProps } from './componentProps'
|
||||||
@ -1429,6 +1430,8 @@ function baseCreateRenderer(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, __DEV__ ? createDevEffectOptions(instance) : prodEffectOptions)
|
}, __DEV__ ? createDevEffectOptions(instance) : prodEffectOptions)
|
||||||
|
// #1801 mark it to allow recursive updates
|
||||||
|
;(instance.update as SchedulerJob).allowRecurse = true
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateComponentPreRender = (
|
const updateComponentPreRender = (
|
||||||
|
@ -8,12 +8,18 @@ export interface SchedulerJob {
|
|||||||
*/
|
*/
|
||||||
id?: number
|
id?: number
|
||||||
/**
|
/**
|
||||||
* Indicates this is a watch() callback and is allowed to trigger itself.
|
* Indicates whether the job is allowed to recursively trigger itself.
|
||||||
* A watch callback doesn't track its dependencies so if it triggers itself
|
* By default, a job cannot trigger itself because some built-in method calls,
|
||||||
* again, it's likely intentional and it is the user's responsibility to
|
* e.g. Array.prototype.push actually performs reads as well (#1740) which
|
||||||
* perform recursive state mutation that eventually stabilizes.
|
* 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
|
let isFlushing = false
|
||||||
@ -54,7 +60,7 @@ export function queueJob(job: SchedulerJob) {
|
|||||||
(!queue.length ||
|
(!queue.length ||
|
||||||
!queue.includes(
|
!queue.includes(
|
||||||
job,
|
job,
|
||||||
isFlushing && job.cb ? flushIndex + 1 : flushIndex
|
isFlushing && job.allowRecurse ? flushIndex + 1 : flushIndex
|
||||||
)) &&
|
)) &&
|
||||||
job !== currentPreFlushParentJob
|
job !== currentPreFlushParentJob
|
||||||
) {
|
) {
|
||||||
@ -86,7 +92,10 @@ function queueCb(
|
|||||||
if (!isArray(cb)) {
|
if (!isArray(cb)) {
|
||||||
if (
|
if (
|
||||||
!activeQueue ||
|
!activeQueue ||
|
||||||
!activeQueue.includes(cb, (cb as SchedulerJob).cb ? index + 1 : index)
|
!activeQueue.includes(
|
||||||
|
cb,
|
||||||
|
(cb as SchedulerJob).allowRecurse ? index + 1 : index
|
||||||
|
)
|
||||||
) {
|
) {
|
||||||
pendingQueue.push(cb)
|
pendingQueue.push(cb)
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user