面试官:你是怎么处理vue项目中的错误的?
一、错误类型
任何一个框架,对于错误的处理都是一种必备的能力
在Vue
中,则是定义了一套对应的错误处理规则给到使用者,且在源代码级别,对部分必要的过程做了一定的错误处理。
主要的错误来源包括:
二、如何处理
后端接口错误
通过axios
的interceptor
实现网络请求的response
先进行一层拦截
1 2 3 4 5 6 7 8 9 10 11 12 13
| apiClient.interceptors.response.use( response => { return response; }, error => { if (error.response.status == 401) { router.push({ name: "Login" }); } else { message.error("出错了"); return Promise.reject(error); } } );
|
代码逻辑问题
全局设置错误处理
设置全局错误处理函数
1 2 3 4 5
| Vue.config.errorHandler = function (err, vm, info) { }
|
errorHandler
指定组件的渲染和观察期间未捕获错误的处理函数。这个处理函数被调用时,可获取错误信息和 Vue
实例
不过值得注意的是,在不同 Vue
版本中,该全局 API
作用的范围会有所不同:
从 2.2.0 起,这个钩子也会捕获组件生命周期钩子里的错误。同样的,当这个钩子是 undefined
时,被捕获的错误会通过 console.error
输出而避免应用崩
从 2.4.0 起,这个钩子也会捕获 Vue 自定义事件处理函数内部的错误了
从 2.6.0 起,这个钩子也会捕获 v-on
DOM 监听器内部抛出的错误。另外,如果任何被覆盖的钩子或处理函数返回一个 Promise 链 (例如 async 函数),则来自其 Promise 链的错误也会被处理
生命周期钩子
errorCaptured
是 2.5.0 新增的一个生命钩子函数,当捕获到一个来自子孙组件的错误时被调用
基本类型
1
| (err: Error, vm: Component, info: string) => ?boolean
|
此钩子会收到三个参数:错误对象、发生错误的组件实例以及一个包含错误来源信息的字符串。此钩子可以返回 false
以阻止该错误继续向上传播
参考官网,错误传播规则如下:
- 默认情况下,如果全局的
config.errorHandler
被定义,所有的错误仍会发送它,因此这些错误仍然会向单一的分析服务的地方进行汇报
- 如果一个组件的继承或父级从属链路中存在多个
errorCaptured
钩子,则它们将会被相同的错误逐个唤起。
- 如果此
errorCaptured
钩子自身抛出了一个错误,则这个新错误和原本被捕获的错误都会发送给全局的 config.errorHandler
- 一个
errorCaptured
钩子能够返回 false
以阻止错误继续向上传播。本质上是说“这个错误已经被搞定了且应该被忽略”。它会阻止其它任何会被这个错误唤起的 errorCaptured
钩子和全局的 config.errorHandler
下面来看个例子
定义一个父组件cat
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| Vue.component('cat', { template:` <div> <h1>Cat: </h1> <slot></slot> </div>`, props:{ name:{ required:true, type:String } }, errorCaptured(err,vm,info) { console.log(`cat EC: ${err.toString()}\ninfo: ${info}`); return false; }
});
|
定义一个子组件kitten
,其中dontexist()
并没有定义,存在错误
1 2 3 4 5 6 7 8 9
| Vue.component('kitten', { template:'<div><h1>Kitten: {{ dontexist() }}</h1></div>', props:{ name:{ required:true, type:String } } });
|
页面中使用组件
1 2 3 4 5
| <div id="app" v-cloak> <cat name="my cat"> <kitten></kitten> </cat> </div>
|
在父组件的errorCaptured
则能够捕获到信息
1 2
| cat EC: TypeError: dontexist is not a function info: render
|
三、源码分析
异常处理源码
源码位置:/src/core/util/error.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112
| import config from '../config' import { warn } from './debug'
import { inBrowser, inWeex } from './env'
import { isPromise } from 'shared/util'
import { pushTarget, popTarget } from '../observer/dep'
export function handleError (err: Error, vm: any, info: string) { pushTarget() try { if (vm) { let cur = vm while ((cur = cur.$parent)) { const hooks = cur.$options.errorCaptured if (hooks) { for (let i = 0; i < hooks.length; i++) { try { const capture = hooks[i].call(cur, err, vm, info) === false if (capture) return } catch (e) { globalHandleError(e, cur, 'errorCaptured hook') } } } } } globalHandleError(err, vm, info) } finally { popTarget() } }
export function invokeWithErrorHandling ( handler: Function, context: any, args: null | any[], vm: any, info: string ) { let res try { res = args ? handler.apply(context, args) : handler.call(context) if (res && !res._isVue && isPromise(res) && !res._handled) { res.catch(e => handleError(e, vm, info + ` (Promise/async)`)) res._handled = true } } catch (e) { handleError(e, vm, info) } return res }
function globalHandleError (err, vm, info) { if (config.errorHandler) { try { return config.errorHandler.call(null, err, vm, info) } catch (e) { if (e !== err) { logError(e, null, 'config.errorHandler') } } } logError(err, vm, info) }
function logError (err, vm, info) { if (process.env.NODE_ENV !== 'production') { warn(`Error in ${info}: "${err.toString()}"`, vm) } if ((inBrowser || inWeex) && typeof console !== 'undefined') { console.error(err) } else { throw err } }
|
小结
handleError
在需要捕获异常的地方调用,首先获取到报错的组件,之后递归查找当前组件的父组件,依次调用errorCaptured
方法,在遍历调用完所有 errorCaptured
方法或 errorCaptured
方法有报错时,调用 globalHandleError
方法
globalHandleError
调用全局的 errorHandler
方法,再通过logError
判断环境输出错误信息
invokeWithErrorHandling
更好的处理异步错误信息
logError
判断环境,选择不同的抛错方式。非生产环境下,调用warn
方法处理错误
参考文献