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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user