mirror of
https://gitee.com/vuejs/vue.git
synced 2024-12-03 12:38:24 +08:00
tweak error handling
This commit is contained in:
parent
93fb4df3c4
commit
2732fec59e
@ -4,7 +4,7 @@ import config from '../config'
|
||||
import VNode, { emptyVNode } from '../vdom/vnode'
|
||||
import { normalizeChildren } from '../vdom/helpers'
|
||||
import {
|
||||
warn, bind, isObject, toObject,
|
||||
warn, formatComponentName, bind, isObject, toObject,
|
||||
nextTick, resolveAsset, _toString, toNumber
|
||||
} from '../util/index'
|
||||
|
||||
@ -48,19 +48,38 @@ export function renderMixin (Vue: Class<Component>) {
|
||||
_parentVnode
|
||||
} = vm.$options
|
||||
|
||||
if (staticRenderFns && !this._staticTrees) {
|
||||
this._staticTrees = []
|
||||
if (staticRenderFns && !vm._staticTrees) {
|
||||
vm._staticTrees = []
|
||||
}
|
||||
// set parent vnode. this allows render functions to have access
|
||||
// to the data on the placeholder node.
|
||||
this.$vnode = _parentVnode
|
||||
vm.$vnode = _parentVnode
|
||||
// resolve slots. becaues slots are rendered in parent scope,
|
||||
// we set the activeInstance to parent.
|
||||
if (_renderChildren) {
|
||||
resolveSlots(vm, _renderChildren)
|
||||
}
|
||||
// render self
|
||||
let vnode = render.call(vm._renderProxy, vm.$createElement)
|
||||
let vnode
|
||||
try {
|
||||
vnode = render.call(vm._renderProxy, vm.$createElement)
|
||||
} catch (e) {
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
warn(`Error when rendering ${formatComponentName(vm)}:`)
|
||||
}
|
||||
/* istanbul ignore else */
|
||||
if (config.errorHandler) {
|
||||
config.errorHandler.call(null, e, vm)
|
||||
} else {
|
||||
if (config._isServer) {
|
||||
throw e
|
||||
} else {
|
||||
setTimeout(() => { throw e }, 0)
|
||||
}
|
||||
}
|
||||
// return previous vnode to prevent render error causing blank component
|
||||
vnode = vm._vnode
|
||||
}
|
||||
// return empty vnode in case the render function errored out
|
||||
if (!(vnode instanceof VNode)) {
|
||||
if (process.env.NODE_ENV !== 'production' && Array.isArray(vnode)) {
|
||||
|
@ -82,9 +82,9 @@ function runSchedulerQueue (queue: Array<Watcher>) {
|
||||
if (circular[id] > config._maxUpdateCount) {
|
||||
warn(
|
||||
'You may have an infinite update loop ' + (
|
||||
watcher === watcher.vm && watcher.vm._watcher
|
||||
? `in a component render function.`
|
||||
: `in watcher with expression "${watcher.expression}"`
|
||||
watcher.user
|
||||
? `in watcher with expression "${watcher.expression}"`
|
||||
: `in a component render function.`
|
||||
),
|
||||
watcher.vm
|
||||
)
|
||||
|
@ -83,33 +83,7 @@ export default class Watcher {
|
||||
*/
|
||||
get () {
|
||||
pushTarget(this)
|
||||
let value: any
|
||||
try {
|
||||
value = this.getter.call(this.vm, this.vm)
|
||||
} catch (e) {
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
if (this.user) {
|
||||
warn(
|
||||
'Error when evaluating watcher with getter: ' + this.expression,
|
||||
this.vm
|
||||
)
|
||||
} else {
|
||||
warn(
|
||||
'Error during component render',
|
||||
this.vm
|
||||
)
|
||||
}
|
||||
}
|
||||
/* istanbul ignore else */
|
||||
if (config.errorHandler) {
|
||||
config.errorHandler.call(null, e, this.vm)
|
||||
} else {
|
||||
console.error(e)
|
||||
}
|
||||
// return old value when evaluation fails so the current UI is preserved
|
||||
// if the error was somehow handled by user
|
||||
value = this.value
|
||||
}
|
||||
const value = this.getter.call(this.vm, this.vm)
|
||||
// "touch" every property so they are all tracked as
|
||||
// dependencies for deep watching
|
||||
if (this.deep) {
|
||||
|
@ -1,5 +1,4 @@
|
||||
import config from '../config'
|
||||
import { hyphenate } from 'shared/util'
|
||||
|
||||
let warn
|
||||
let formatComponentName
|
||||
@ -9,21 +8,28 @@ if (process.env.NODE_ENV !== 'production') {
|
||||
|
||||
warn = (msg, vm) => {
|
||||
if (hasConsole && (!config.silent)) {
|
||||
console.error('[Vue warn]: ' + msg + (vm ? formatComponentName(vm) : ''))
|
||||
console.error(`[Vue warn]: ${msg} ` + (
|
||||
vm ? formatLocation(formatComponentName(vm)) : ''
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
formatComponentName = vm => {
|
||||
if (vm.$root === vm) {
|
||||
return ' (found in root instance)'
|
||||
return 'root instance'
|
||||
}
|
||||
const name = vm._isVue
|
||||
? vm.$options.name || vm.$options._componentTag
|
||||
: vm.name
|
||||
return name
|
||||
? ' (found in component: <' + hyphenate(name) + '>)'
|
||||
: ' (found in anonymous component. Use the "name" option for better debugging messages)'
|
||||
return name ? `component <${name}>` : `anonymous component`
|
||||
}
|
||||
|
||||
const formatLocation = str => {
|
||||
if (str === 'anonymous component') {
|
||||
str += ` - use the "name" option for better debugging messages.)`
|
||||
}
|
||||
return `(found in ${str})`
|
||||
}
|
||||
}
|
||||
|
||||
export { warn }
|
||||
export { warn, formatComponentName }
|
||||
|
@ -276,4 +276,31 @@ describe('Component', () => {
|
||||
expect(vm.$el.className).toBe('test red')
|
||||
}).then(done)
|
||||
})
|
||||
|
||||
it('catch component render error and preserve previous vnode', done => {
|
||||
const spy = jasmine.createSpy()
|
||||
Vue.config.errorHandler = spy
|
||||
const vm = new Vue({
|
||||
data: {
|
||||
a: {
|
||||
b: 123
|
||||
}
|
||||
},
|
||||
render (h) {
|
||||
return h('div', [this.a.b])
|
||||
}
|
||||
}).$mount()
|
||||
expect(vm.$el.textContent).toBe('123')
|
||||
expect(spy).not.toHaveBeenCalled()
|
||||
vm.a = null
|
||||
waitForUpdate(() => {
|
||||
expect('Error when rendering root instance').toHaveBeenWarned()
|
||||
expect(spy).toHaveBeenCalled()
|
||||
expect(vm.$el.textContent).toBe('123') // should preserve rendered DOM
|
||||
vm.a = { b: 234 }
|
||||
}).then(() => {
|
||||
expect(vm.$el.textContent).toBe('234') // should be able to recover
|
||||
Vue.config.errorHandler = null
|
||||
}).then(done)
|
||||
})
|
||||
})
|
||||
|
@ -178,27 +178,4 @@ describe('Watcher', () => {
|
||||
new Watcher(vm, 'd.e + c', spy)
|
||||
expect('Failed watching path:').toHaveBeenWarned()
|
||||
})
|
||||
|
||||
it('catch getter error', () => {
|
||||
Vue.config.errorHandler = spy
|
||||
const err = new Error()
|
||||
const vm = new Vue({
|
||||
render () { throw err }
|
||||
}).$mount()
|
||||
expect('Error during component render').toHaveBeenWarned()
|
||||
expect(spy).toHaveBeenCalledWith(err, vm)
|
||||
Vue.config.errorHandler = null
|
||||
})
|
||||
|
||||
it('catch user watcher error', () => {
|
||||
Vue.config.errorHandler = spy
|
||||
new Watcher(vm, function () {
|
||||
return this.a.b.c
|
||||
}, () => {}, { user: true })
|
||||
expect('Error when evaluating watcher').toHaveBeenWarned()
|
||||
expect(spy).toHaveBeenCalled()
|
||||
expect(spy.calls.argsFor(0)[0] instanceof TypeError).toBe(true)
|
||||
expect(spy.calls.argsFor(0)[1]).toBe(vm)
|
||||
Vue.config.errorHandler = null
|
||||
})
|
||||
})
|
||||
|
Loading…
Reference in New Issue
Block a user