diff --git a/packages/runtime-core/src/renderer.ts b/packages/runtime-core/src/renderer.ts index 497c4b3d1af..2a2ac944da7 100644 --- a/packages/runtime-core/src/renderer.ts +++ b/packages/runtime-core/src/renderer.ts @@ -1599,6 +1599,11 @@ function baseCreateRenderer( } } }, __DEV__ ? createDevEffectOptions(instance) : prodEffectOptions) + + if (__DEV__) { + // @ts-ignore + instance.update.ownerInstance = instance + } } const updateComponentPreRender = ( diff --git a/packages/runtime-core/src/scheduler.ts b/packages/runtime-core/src/scheduler.ts index 372866c8093..8ab5a23108d 100644 --- a/packages/runtime-core/src/scheduler.ts +++ b/packages/runtime-core/src/scheduler.ts @@ -1,6 +1,8 @@ import { ErrorCodes, callWithErrorHandling } from './errorHandling' import { isArray } from '@vue/shared' import { ComponentPublicInstance } from './componentPublicInstance' +import { ComponentInternalInstance, getComponentName } from './component' +import { warn } from './warning' export interface SchedulerJob { (): void @@ -22,6 +24,7 @@ export interface SchedulerJob { * stabilizes (#1727). */ allowRecurse?: boolean + ownerInstance?: ComponentInternalInstance } export type SchedulerCb = Function & { id?: number } @@ -164,8 +167,11 @@ export function flushPreFlushCbs( preFlushIndex < activePreFlushCbs.length; preFlushIndex++ ) { - if (__DEV__) { + if ( + __DEV__ && checkRecursiveUpdates(seen!, activePreFlushCbs[preFlushIndex]) + ) { + continue } activePreFlushCbs[preFlushIndex]() } @@ -200,8 +206,11 @@ export function flushPostFlushCbs(seen?: CountMap) { postFlushIndex < activePostFlushCbs.length; postFlushIndex++ ) { - if (__DEV__) { + if ( + __DEV__ && checkRecursiveUpdates(seen!, activePostFlushCbs[postFlushIndex]) + ) { + continue } activePostFlushCbs[postFlushIndex]() } @@ -235,8 +244,8 @@ function flushJobs(seen?: CountMap) { for (flushIndex = 0; flushIndex < queue.length; flushIndex++) { const job = queue[flushIndex] if (job) { - if (__DEV__) { - checkRecursiveUpdates(seen!, job) + if (__DEV__ && checkRecursiveUpdates(seen!, job)) { + continue } callWithErrorHandling(job, null, ErrorCodes.SCHEDULER) } @@ -263,13 +272,18 @@ function checkRecursiveUpdates(seen: CountMap, fn: SchedulerJob | SchedulerCb) { } else { const count = seen.get(fn)! if (count > RECURSION_LIMIT) { - throw new Error( - `Maximum recursive updates exceeded. ` + + const instance = (fn as SchedulerJob).ownerInstance + const componentName = instance && getComponentName(instance.type) + warn( + `Maximum recursive updates exceeded${ + componentName ? ` in component <${componentName}>` : `` + }. ` + `This means you have a reactive effect that is mutating its own ` + `dependencies and thus recursively triggering itself. Possible sources ` + `include component template, render function, updated hook or ` + `watcher source function.` ) + return true } else { seen.set(fn, count + 1) }