fix(runtime-core): error handling for created/beforeCreate hooks

fix #2268
This commit is contained in:
Evan You 2020-10-07 16:58:19 -04:00
parent d744b8a2dc
commit b392fe419c
2 changed files with 66 additions and 15 deletions

View File

@ -181,6 +181,41 @@ describe('error handling', () => {
expect(fn).toHaveBeenCalledWith(err, 'setup function')
})
// unlike other lifecycle hooks, created/beforeCreate are called as part of
// the options API initiualization process instead of by the renderer.
test('in created/beforeCreate hook', () => {
const err = new Error('foo')
const fn = jest.fn()
const Comp = {
setup() {
onErrorCaptured((err, instance, info) => {
fn(err, info)
return false
})
return () => [h(Child1), h(Child2)]
}
}
const Child1 = {
created() {
throw err
},
render() {}
}
const Child2 = {
beforeCreate() {
throw err
},
render() {}
}
render(h(Comp), nodeOps.createElement('div'))
expect(fn).toHaveBeenCalledWith(err, 'created hook')
expect(fn).toHaveBeenCalledWith(err, 'beforeCreate hook')
})
test('in render function', () => {
const err = new Error('foo')
const fn = jest.fn()

View File

@ -5,7 +5,8 @@ import {
ComponentInternalOptions,
Component,
ConcreteComponent,
InternalRenderFunction
InternalRenderFunction,
LifecycleHooks
} from './component'
import {
isFunction,
@ -55,6 +56,7 @@ import {
} from './componentPublicInstance'
import { warn } from './warning'
import { VNodeChild } from './vnode'
import { callWithAsyncErrorHandling } from './errorHandling'
/**
* Interface for declaring custom options.
@ -472,7 +474,13 @@ export function applyOptions(
// applyOptions is called non-as-mixin once per instance
if (!asMixin) {
isInBeforeCreate = true
callSyncHook('beforeCreate', options, publicThis, globalMixins)
callSyncHook(
'beforeCreate',
LifecycleHooks.BEFORE_CREATE,
options,
instance,
globalMixins
)
isInBeforeCreate = false
// global mixins are applied first
applyMixins(instance, globalMixins, deferredData, deferredWatch)
@ -662,7 +670,13 @@ export function applyOptions(
// lifecycle options
if (!asMixin) {
callSyncHook('created', options, publicThis, globalMixins)
callSyncHook(
'created',
LifecycleHooks.CREATED,
options,
instance,
globalMixins
)
}
if (beforeMount) {
onBeforeMount(beforeMount.bind(publicThis))
@ -707,52 +721,54 @@ export function applyOptions(
function callSyncHook(
name: 'beforeCreate' | 'created',
type: LifecycleHooks,
options: ComponentOptions,
ctx: ComponentPublicInstance,
instance: ComponentInternalInstance,
globalMixins: ComponentOptions[]
) {
callHookFromMixins(name, globalMixins, ctx)
callHookFromMixins(name, type, globalMixins, instance)
const { extends: base, mixins } = options
if (base) {
callHookFromExtends(name, base, ctx)
callHookFromExtends(name, type, base, instance)
}
if (mixins) {
callHookFromMixins(name, mixins, ctx)
callHookFromMixins(name, type, mixins, instance)
}
const selfHook = options[name]
if (selfHook) {
selfHook.call(ctx)
callWithAsyncErrorHandling(selfHook.bind(instance.proxy!), instance, type)
}
}
function callHookFromExtends(
name: 'beforeCreate' | 'created',
type: LifecycleHooks,
base: ComponentOptions,
ctx: ComponentPublicInstance
instance: ComponentInternalInstance
) {
if (base.extends) {
callHookFromExtends(name, base.extends, ctx)
callHookFromExtends(name, type, base.extends, instance)
}
const baseHook = base[name]
if (baseHook) {
baseHook.call(ctx)
callWithAsyncErrorHandling(baseHook.bind(instance.proxy!), instance, type)
}
}
function callHookFromMixins(
name: 'beforeCreate' | 'created',
type: LifecycleHooks,
mixins: ComponentOptions[],
ctx: ComponentPublicInstance
instance: ComponentInternalInstance
) {
for (let i = 0; i < mixins.length; i++) {
const chainedMixins = mixins[i].mixins
if (chainedMixins) {
callHookFromMixins(name, chainedMixins, ctx)
callHookFromMixins(name, type, chainedMixins, instance)
}
const fn = mixins[i][name]
if (fn) {
fn.call(ctx)
callWithAsyncErrorHandling(fn.bind(instance.proxy!), instance, type)
}
}
}