fix(runtime-core): errors during component patch should be caught by error handlers

This commit is contained in:
Evan You 2024-07-12 01:24:17 +08:00
parent 3d34f406ac
commit ee0248accf
No known key found for this signature in database
GPG Key ID: 00E9AB7A6704CE0A
4 changed files with 43 additions and 10 deletions

View File

@ -1,4 +1,5 @@
import {
type VNode,
createApp,
defineComponent,
h,
@ -11,6 +12,7 @@ import {
watch,
watchEffect,
} from '@vue/runtime-test'
import { ErrorCodes, ErrorTypeStrings } from '../src/errorHandling'
describe('error handling', () => {
test('propagation', () => {
@ -609,5 +611,33 @@ describe('error handling', () => {
expect(handler).toHaveBeenCalledTimes(1)
})
test('errors in scheduler job with owner instance should be caught', async () => {
let vnode: VNode
const x = ref(0)
const app = createApp({
render() {
return (vnode = vnode || h('div', x.value))
},
})
app.config.errorHandler = vi.fn()
app.mount(nodeOps.createElement('div'))
const error = new Error('error')
Object.defineProperty(vnode!, 'el', {
get() {
throw error
},
})
x.value++
await nextTick()
expect(app.config.errorHandler).toHaveBeenCalledWith(
error,
{},
ErrorTypeStrings[ErrorCodes.COMPONENT_UPDATE],
)
})
// native event handler handling should be tested in respective renderers
})

View File

@ -23,6 +23,7 @@ export enum ErrorCodes {
FUNCTION_REF,
ASYNC_COMPONENT_LOADER,
SCHEDULER,
COMPONENT_UPDATE,
}
export const ErrorTypeStrings: Record<LifecycleHooks | ErrorCodes, string> = {
@ -54,16 +55,15 @@ export const ErrorTypeStrings: Record<LifecycleHooks | ErrorCodes, string> = {
[ErrorCodes.APP_WARN_HANDLER]: 'app warnHandler',
[ErrorCodes.FUNCTION_REF]: 'ref function',
[ErrorCodes.ASYNC_COMPONENT_LOADER]: 'async component loader',
[ErrorCodes.SCHEDULER]:
'scheduler flush. This is likely a Vue internals bug. ' +
'Please open an issue at https://github.com/vuejs/core .',
[ErrorCodes.SCHEDULER]: 'scheduler flush',
[ErrorCodes.COMPONENT_UPDATE]: 'component update',
}
export type ErrorTypes = LifecycleHooks | ErrorCodes
export function callWithErrorHandling(
fn: Function,
instance: ComponentInternalInstance | null,
instance: ComponentInternalInstance | null | undefined,
type: ErrorTypes,
args?: unknown[],
) {
@ -105,7 +105,7 @@ export function callWithAsyncErrorHandling(
export function handleError(
err: unknown,
instance: ComponentInternalInstance | null,
instance: ComponentInternalInstance | null | undefined,
type: ErrorTypes,
throwInDev = true,
) {

View File

@ -1587,6 +1587,7 @@ function baseCreateRenderer(
effect.run()
}
})
update.i = instance
update.id = instance.uid
// allowRecurse
// #1801, #2043 component render effects should allow recursive updates
@ -1599,7 +1600,6 @@ function baseCreateRenderer(
effect.onTrigger = instance.rtg
? e => invokeArrayFns(instance.rtg!, e)
: void 0
update.ownerInstance = instance
}
update()

View File

@ -26,9 +26,8 @@ export interface SchedulerJob extends Function {
/**
* Attached by renderer.ts when setting up a component's render effect
* Used to obtain component information when reporting max recursive updates.
* dev only.
*/
ownerInstance?: ComponentInternalInstance
i?: ComponentInternalInstance
}
export type SchedulerJobs = SchedulerJob | SchedulerJob[]
@ -240,7 +239,11 @@ function flushJobs(seen?: CountMap) {
if (__DEV__ && check(job)) {
continue
}
callWithErrorHandling(job, null, ErrorCodes.SCHEDULER)
callWithErrorHandling(
job,
job.i,
job.i ? ErrorCodes.COMPONENT_UPDATE : ErrorCodes.SCHEDULER,
)
}
}
} finally {
@ -265,7 +268,7 @@ function checkRecursiveUpdates(seen: CountMap, fn: SchedulerJob) {
} else {
const count = seen.get(fn)!
if (count > RECURSION_LIMIT) {
const instance = fn.ownerInstance
const instance = fn.i
const componentName = instance && getComponentName(instance.type)
handleError(
`Maximum recursive updates exceeded${