fix(watch): exhaust pre-flush watchers + avoid duplicate render by pre-flush watchers
close #1777
This commit is contained in:
parent
b5f91ff570
commit
a0e34cee4a
@ -436,6 +436,7 @@ describe('api: watch', () => {
|
|||||||
it('flush: pre watcher watching props should fire before child update', async () => {
|
it('flush: pre watcher watching props should fire before child update', async () => {
|
||||||
const a = ref(0)
|
const a = ref(0)
|
||||||
const b = ref(0)
|
const b = ref(0)
|
||||||
|
const c = ref(0)
|
||||||
const calls: string[] = []
|
const calls: string[] = []
|
||||||
|
|
||||||
const Comp = {
|
const Comp = {
|
||||||
@ -444,11 +445,22 @@ describe('api: watch', () => {
|
|||||||
watch(
|
watch(
|
||||||
() => props.a + props.b,
|
() => props.a + props.b,
|
||||||
() => {
|
() => {
|
||||||
calls.push('watcher')
|
calls.push('watcher 1')
|
||||||
|
c.value++
|
||||||
|
},
|
||||||
|
{ flush: 'pre' }
|
||||||
|
)
|
||||||
|
|
||||||
|
// #1777 chained pre-watcher
|
||||||
|
watch(
|
||||||
|
c,
|
||||||
|
() => {
|
||||||
|
calls.push('watcher 2')
|
||||||
},
|
},
|
||||||
{ flush: 'pre' }
|
{ flush: 'pre' }
|
||||||
)
|
)
|
||||||
return () => {
|
return () => {
|
||||||
|
c.value
|
||||||
calls.push('render')
|
calls.push('render')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -469,7 +481,7 @@ describe('api: watch', () => {
|
|||||||
a.value++
|
a.value++
|
||||||
b.value++
|
b.value++
|
||||||
await nextTick()
|
await nextTick()
|
||||||
expect(calls).toEqual(['render', 'watcher', 'render'])
|
expect(calls).toEqual(['render', 'watcher 1', 'watcher 2', 'render'])
|
||||||
})
|
})
|
||||||
|
|
||||||
it('deep', async () => {
|
it('deep', async () => {
|
||||||
|
@ -1430,7 +1430,7 @@ function baseCreateRenderer(
|
|||||||
instance.next = null
|
instance.next = null
|
||||||
updateProps(instance, nextVNode.props, prevProps, optimized)
|
updateProps(instance, nextVNode.props, prevProps, optimized)
|
||||||
updateSlots(instance, nextVNode.children)
|
updateSlots(instance, nextVNode.children)
|
||||||
runPreflushJobs()
|
runPreflushJobs(instance.update)
|
||||||
}
|
}
|
||||||
|
|
||||||
const patchChildren: PatchChildrenFn = (
|
const patchChildren: PatchChildrenFn = (
|
||||||
|
@ -27,6 +27,7 @@ let flushIndex = 0
|
|||||||
let pendingPostFlushCbs: Function[] | null = null
|
let pendingPostFlushCbs: Function[] | null = null
|
||||||
let pendingPostFlushIndex = 0
|
let pendingPostFlushIndex = 0
|
||||||
let hasPendingPreFlushJobs = false
|
let hasPendingPreFlushJobs = false
|
||||||
|
let currentPreFlushParentJob: SchedulerJob | null = null
|
||||||
|
|
||||||
const RECURSION_LIMIT = 100
|
const RECURSION_LIMIT = 100
|
||||||
type CountMap = Map<SchedulerJob | Function, number>
|
type CountMap = Map<SchedulerJob | Function, number>
|
||||||
@ -44,9 +45,16 @@ export function queueJob(job: SchedulerJob) {
|
|||||||
// allow it recursively trigger itself - it is the user's responsibility to
|
// allow it recursively trigger itself - it is the user's responsibility to
|
||||||
// ensure it doesn't end up in an infinite loop.
|
// ensure it doesn't end up in an infinite loop.
|
||||||
if (
|
if (
|
||||||
!queue.length ||
|
(!queue.length ||
|
||||||
!queue.includes(job, isFlushing && job.cb ? flushIndex + 1 : flushIndex)
|
!queue.includes(
|
||||||
|
job,
|
||||||
|
isFlushing && job.cb ? flushIndex + 1 : flushIndex
|
||||||
|
)) &&
|
||||||
|
job !== currentPreFlushParentJob
|
||||||
) {
|
) {
|
||||||
|
if (job.id && job.id > 0) {
|
||||||
|
debugger
|
||||||
|
}
|
||||||
queue.push(job)
|
queue.push(job)
|
||||||
if ((job.id as number) < 0) hasPendingPreFlushJobs = true
|
if ((job.id as number) < 0) hasPendingPreFlushJobs = true
|
||||||
queueFlush()
|
queueFlush()
|
||||||
@ -60,16 +68,27 @@ export function invalidateJob(job: SchedulerJob) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function runPreflushJobs() {
|
/**
|
||||||
|
* Run flush: 'pre' watcher callbacks. This is only called in
|
||||||
|
* `updateComponentPreRender` to cover the case where pre-flush watchers are
|
||||||
|
* triggered by the change of a component's props. This means the scheduler is
|
||||||
|
* already flushing and we are already inside the component's update effect,
|
||||||
|
* right when the render function is about to be called. So if the watcher
|
||||||
|
* triggers the same component to update, we don't want it to be queued (this
|
||||||
|
* is checked via `currentPreFlushParentJob`).
|
||||||
|
*/
|
||||||
|
export function runPreflushJobs(parentJob: SchedulerJob) {
|
||||||
if (hasPendingPreFlushJobs) {
|
if (hasPendingPreFlushJobs) {
|
||||||
|
currentPreFlushParentJob = parentJob
|
||||||
hasPendingPreFlushJobs = false
|
hasPendingPreFlushJobs = false
|
||||||
for (let job, i = queue.length - 1; i > flushIndex; i--) {
|
for (let job, i = flushIndex + 1; i < queue.length; i++) {
|
||||||
job = queue[i]
|
job = queue[i]
|
||||||
if (job && (job.id as number) < 0) {
|
if (job && (job.id as number) < 0) {
|
||||||
job()
|
job()
|
||||||
queue[i] = null
|
queue[i] = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
currentPreFlushParentJob = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user