fix(watch): exhaust pre-flush watchers + avoid duplicate render by pre-flush watchers

close #1777
This commit is contained in:
Evan You 2020-08-04 13:20:09 -04:00
parent b5f91ff570
commit a0e34cee4a
3 changed files with 38 additions and 7 deletions

View File

@ -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 () => {

View File

@ -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 = (

View File

@ -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
} }
} }