一、錯(cuò)誤類型
任何一個(gè)框架,對于錯(cuò)誤的處理都是一種必備的能力
在Vue 中,則是定義了一套對應(yīng)的錯(cuò)誤處理規(guī)則給到使用者,且在源代碼級別,對部分必要的過程做了一定的錯(cuò)誤處理。
主要的錯(cuò)誤來源包括:
- 后端接口錯(cuò)誤
- 代碼中本身邏輯錯(cuò)誤
二、如何處理
后端接口錯(cuò)誤
通過axIOS的interceptor實(shí)現(xiàn)網(wǎng)絡(luò)請求的response先進(jìn)行一層攔截
apiClient.interceptors.response.use(
response => {
return response;
},
error => {
if (error.response.status == 401) {
router.push({ name: "Login" });
} else {
message.error("出錯(cuò)了");
return Promise.reject(error);
}
}
);
代碼邏輯問題
全局設(shè)置錯(cuò)誤處理
設(shè)置全局錯(cuò)誤處理函數(shù)
Vue.config.errorHandler = function (err, vm, info) {
// handle error
// `info` 是 Vue 特定的錯(cuò)誤信息,比如錯(cuò)誤所在的生命周期鉤子
// 只在 2.2.0+ 可用
}
errorHandler指定組件的渲染和觀察期間未捕獲錯(cuò)誤的處理函數(shù)。這個(gè)處理函數(shù)被調(diào)用時(shí),可獲取錯(cuò)誤信息和 Vue 實(shí)例
不過值得注意的是,在不同Vue 版本中,該全局 API 作用的范圍會有所不同:
從 2.2.0 起,這個(gè)鉤子也會捕獲組件生命周期鉤子里的錯(cuò)誤。同樣的,當(dāng)這個(gè)鉤子是 undefined 時(shí),被捕獲的錯(cuò)誤會通過 console.error 輸出而避免應(yīng)用崩
從 2.4.0 起,這個(gè)鉤子也會捕獲 Vue 自定義事件處理函數(shù)內(nèi)部的錯(cuò)誤了
從 2.6.0 起,這個(gè)鉤子也會捕獲 v-on DOM 監(jiān)聽器內(nèi)部拋出的錯(cuò)誤。另外,如果任何被覆蓋的鉤子或處理函數(shù)返回一個(gè) Promise 鏈 (例如 async 函數(shù)),則來自其 Promise 鏈的錯(cuò)誤也會被處理
生命周期鉤子
errorCaptured是 2.5.0 新增的一個(gè)生命鉤子函數(shù),當(dāng)捕獲到一個(gè)來自子孫組件的錯(cuò)誤時(shí)被調(diào)用
基本類型
(err: Error, vm: Component, info: string) => ?boolean
此鉤子會收到三個(gè)參數(shù):錯(cuò)誤對象、發(fā)生錯(cuò)誤的組件實(shí)例以及一個(gè)包含錯(cuò)誤來源信息的字符串。此鉤子可以返回 false 以阻止該錯(cuò)誤繼續(xù)向上傳播
參考官網(wǎng),錯(cuò)誤傳播規(guī)則如下:
- 默認(rèn)情況下,如果全局的 config.errorHandler 被定義,所有的錯(cuò)誤仍會發(fā)送它,因此這些錯(cuò)誤仍然會向單一的分析服務(wù)的地方進(jìn)行匯報(bào)
- 如果一個(gè)組件的繼承或父級從屬鏈路中存在多個(gè) errorCaptured 鉤子,則它們將會被相同的錯(cuò)誤逐個(gè)喚起。
- 如果此 errorCaptured 鉤子自身拋出了一個(gè)錯(cuò)誤,則這個(gè)新錯(cuò)誤和原本被捕獲的錯(cuò)誤都會發(fā)送給全局的 config.errorHandler
- 一個(gè) errorCaptured 鉤子能夠返回 false 以阻止錯(cuò)誤繼續(xù)向上傳播。本質(zhì)上是說“這個(gè)錯(cuò)誤已經(jīng)被搞定了且應(yīng)該被忽略”。它會阻止其它任何會被這個(gè)錯(cuò)誤喚起的 errorCaptured 鉤子和全局的 config.errorHandler
下面來看個(gè)例子
定義一個(gè)父組件cat
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;
}
});
定義一個(gè)子組件kitten,其中dontexist()并沒有定義,存在錯(cuò)誤
Vue.component('kitten', {
template:'<div><h1>Kitten: {{ dontexist() }}</h1></div>',
props:{
name:{
required:true,
type:String
}
}
});
頁面中使用組件
<div id="App" v-cloak>
<cat name="my cat">
<kitten></kitten>
</cat>
</div>
在父組件的errorCaptured則能夠捕獲到信息
cat EC: TypeError: dontexist is not a function
info: render
三、源碼分析
異常處理源碼
源碼位置:/src/core/util/error.js
// Vue 全局配置,也就是上面的Vue.config
import config from '../config'
import { warn } from './debug'
// 判斷環(huán)境
import { inBrowser, inWeex } from './env'
// 判斷是否是Promise,通過val.then === 'function' && val.catch === 'function', val !=== null && val !== undefined
import { isPromise } from 'shared/util'
// 當(dāng)錯(cuò)誤函數(shù)處理錯(cuò)誤時(shí),停用deps跟蹤以避免可能出現(xiàn)的infinite rendering
// 解決以下出現(xiàn)的問題https://github.com/vuejs/vuex/issues/1505的問題
import { pushTarget, popTarget } from '../observer/dep'
export function handleError (err: Error, vm: any, info: string) {
// Deactivate deps tracking while processing error handler to avoid possible infinite rendering.
pushTarget()
try {
// vm指當(dāng)前報(bào)錯(cuò)的組件實(shí)例
if (vm) {
let cur = vm
// 首先獲取到報(bào)錯(cuò)的組件,之后遞歸查找當(dāng)前組件的父組件,依次調(diào)用errorCaptured 方法。
// 在遍歷調(diào)用完所有 errorCaptured 方法、或 errorCaptured 方法有報(bào)錯(cuò)時(shí),調(diào)用 globalHandleError 方法
while ((cur = cur.$parent)) {
const hooks = cur.$options.errorCaptured
// 判斷是否存在errorCaptured鉤子函數(shù)
if (hooks) {
// 選項(xiàng)合并的策略,鉤子函數(shù)會被保存在一個(gè)數(shù)組中
for (let i = 0; i < hooks.length; i++) {
// 如果errorCaptured 鉤子執(zhí)行自身拋出了錯(cuò)誤,
// 則用try{}catch{}捕獲錯(cuò)誤,將這個(gè)新錯(cuò)誤和原本被捕獲的錯(cuò)誤都會發(fā)送給全局的config.errorHandler
// 調(diào)用globalHandleError方法
try {
// 當(dāng)前errorCaptured執(zhí)行,根據(jù)返回是否是false值
// 是false,capture = true,阻止其它任何會被這個(gè)錯(cuò)誤喚起的 errorCaptured 鉤子和全局的 config.errorHandler
// 是true capture = fale,組件的繼承或父級從屬鏈路中存在的多個(gè) errorCaptured 鉤子,會被相同的錯(cuò)誤逐個(gè)喚起
// 調(diào)用對應(yīng)的鉤子函數(shù),處理錯(cuò)誤
const capture = hooks[i].call(cur, err, vm, info) === false
if (capture) return
} catch (e) {
globalHandleError(e, cur, 'errorCaptured hook')
}
}
}
}
}
// 除非禁止錯(cuò)誤向上傳播,否則都會調(diào)用全局的錯(cuò)誤處理函數(shù)
globalHandleError(err, vm, info)
} finally {
popTarget()
}
}
// 異步錯(cuò)誤處理函數(shù)
export function invokeWithErrorHandling (
handler: Function,
context: any,
args: null | any[],
vm: any,
info: string
) {
let res
try {
// 根據(jù)參數(shù)選擇不同的handle執(zhí)行方式
res = args ? handler.apply(context, args) : handler.call(context)
// handle返回結(jié)果存在
// res._isVue an flag to avoid this being observed,如果傳入值的_isVue為ture時(shí)(即傳入的值是Vue實(shí)例本身)不會新建observer實(shí)例
// isPromise(res) 判斷val.then === 'function' && val.catch === 'function', val !=== null && val !== undefined
// !res._handled _handle是Promise 實(shí)例的內(nèi)部變量之一,默認(rèn)是false,代表onFulfilled,onRejected是否被處理
if (res && !res._isVue && isPromise(res) && !res._handled) {
res.catch(e => handleError(e, vm, info + ` (Promise/async)`))
// avoid catch triggering multiple times when nested calls
// 避免嵌套調(diào)用時(shí)catch多次的觸發(fā)
res._handled = true
}
} catch (e) {
// 處理執(zhí)行錯(cuò)誤
handleError(e, vm, info)
}
return res
}
//全局錯(cuò)誤處理
function globalHandleError (err, vm, info) {
// 獲取全局配置,判斷是否設(shè)置處理函數(shù),默認(rèn)undefined
// 已配置
if (config.errorHandler) {
// try{}catch{} 住全局錯(cuò)誤處理函數(shù)
try {
// 執(zhí)行設(shè)置的全局錯(cuò)誤處理函數(shù),handle error 想干啥就干啥
return config.errorHandler.call(null, err, vm, info)
} catch (e) {
// 如果開發(fā)者在errorHandler函數(shù)中手動拋出同樣錯(cuò)誤信息throw err
// 判斷err信息是否相等,避免log兩次
// 如果拋出新的錯(cuò)誤信息throw err Error('你好毒'),將會一起log輸出
if (e !== err) {
logError(e, null, 'config.errorHandler')
}
}
}
// 未配置常規(guī)log輸出
logError(err, vm, info)
}
// 錯(cuò)誤輸出函數(shù)
function logError (err, vm, info) {
if (process.env.NODE_ENV !== 'production') {
warn(`Error in ${info}: "${err.toString()}"`, vm)
}
/* istanbul ignore else */
if ((inBrowser || inWeex) && typeof console !== 'undefined') {
console.error(err)
} else {
throw err
}
}
小結(jié)
- handleError在需要捕獲異常的地方調(diào)用,首先獲取到報(bào)錯(cuò)的組件,之后遞歸查找當(dāng)前組件的父組件,依次調(diào)用errorCaptured 方法,在遍歷調(diào)用完所有 errorCaptured 方法或 errorCaptured 方法有報(bào)錯(cuò)時(shí),調(diào)用 globalHandleError 方法
- globalHandleError調(diào)用全局的 errorHandler 方法,再通過logError判斷環(huán)境輸出錯(cuò)誤信息
- invokeWithErrorHandling更好的處理異步錯(cuò)誤信息
- logError判斷環(huán)境,選擇不同的拋錯(cuò)方式。非生產(chǎn)環(huán)境下,調(diào)用warn方法處理錯(cuò)誤
其它錯(cuò)誤
1、在配置路由并引入組件后,報(bào)錯(cuò):
Unknown custom element: <router-link> - did you register the component correctly? For recursive components, make sure to provide the "name" option.
錯(cuò)誤原因:vue-router沒有注冊
解決辦法:
//注冊插件 *****************非常重要,不能忘記
Vue.use(VueRouter)
2、在組件中的標(biāo)簽和樣式中圖片路徑出錯(cuò)時(shí):報(bào)錯(cuò):
Errors while compiling. Reload prevented.
Module not found: Error: Can't resolve '
./src/assets/img/btn_bg.png' in 'E:myStudyvue案例chexian-spasrccomponents'
解決辦法:將圖片的路徑重新書寫
3、在組件中標(biāo)簽沒有閉合,報(bào)錯(cuò):
Errors while compiling. Reload prevented.
./node_modules/[email protected]@vue-loader/lib/template-compiler?{"id":"data-v-00822b28","hasScoped":false,"buble":{"transforms":{}}}!./node_modules/[email protected]@vue-loader/lib/selector.js?type=template&index=0&bustCache!./src/components/BaseProject.vue
(Emitted value instead of an instance of Error)
解決辦法:檢查html代碼
4、在使用less定義變量是報(bào)錯(cuò):
錯(cuò)誤原因:必須用分號結(jié)尾:@imgUrl:'../../assets/img/';
Compiled with problems:
編譯問題
C:myelsrcviewsHomeView.vue
錯(cuò)誤出現(xiàn)文件
3:1 error Mixed spaces and tabs no-mixed-spaces-and-tabs
4:1 error Mixed spaces and tabs no-mixed-spaces-and-tabs
第3行的第一個(gè)字符
第4函的第一個(gè)字符
Mixed spaces and tabs
錯(cuò)誤原因:混合的空格與tab
no-mixed-spaces-and-tabs
錯(cuò)誤規(guī)則: no-mixed-spaces-and-tabs 不準(zhǔn)混空格與tab
2 problems (2 errors, 0 warnings)
2個(gè)問題(2個(gè)錯(cuò)誤,0個(gè)警告)
Compiled with problems:
編譯錯(cuò)誤
ERROR in ./src/views/HomeView.vue?
錯(cuò)誤出現(xiàn)的位置
Unexpected keyword 'const'. (6:0)
第6行第0個(gè)字符有個(gè)不應(yīng)該出現(xiàn)的關(guān)鍵字 const
63 | const user = reactive({ userid: "", pwd: "", code: "" }), | ^ 64 | const rules = reactive({ | ^ 65 | userid: [
第63到64行兩個(gè)^之間有錯(cuò)誤
ERROR in ./src/router/index.ts 10:19-57
錯(cuò)誤發(fā)生在 ./src/router/index.ts 第10行第19個(gè)字符到57字符
Module not found: Error: Can't resolve '../views/admin/AdminVeiw.vue' in 'C:myelsrcrouter'
,模塊找不的 不能resolve(兌現(xiàn),發(fā)現(xiàn),解決)
../views/admin/AdminVeiw.vue
在C:myelsrcrouter
總結(jié):文件
../views/admin/AdminVeiw.vue(文件名/路徑發(fā)生錯(cuò)誤)
本地開發(fā)環(huán)境請求服務(wù)器接口跨域的問題
上面的這個(gè)報(bào)錯(cuò)大家都不會陌生,報(bào)錯(cuò)是說沒有訪問權(quán)限(跨域問題)。本地開發(fā)項(xiàng)目請求服務(wù)器接口的時(shí)候,因?yàn)榭蛻舳说耐床呗裕瑢?dǎo)致了跨域的問題。
下面先演示一個(gè)沒有配置允許本地跨域的的情況:
可以看到,此時(shí)我們點(diǎn)擊獲取數(shù)據(jù),瀏覽器提示我們跨域了。所以我們訪問不到數(shù)據(jù)。
那么接下來我們演示設(shè)置允許跨域后的數(shù)據(jù)獲取情況:
注意:配置好后一定要關(guān)閉原來的server,重新npm run dev啟動項(xiàng)目。不然無效。
注意:配置好后一定要關(guān)閉原來的server,重新npm run dev啟動項(xiàng)目。不然無效。
我們在1出設(shè)置了允許本地跨域,在2處,要注意我們訪問接口時(shí),寫的是/api,此處的/api指代的就是我們要請求的接口域名。如果我們不想每次接口都帶上/api,可以更改axios的默認(rèn)配置axios.defaults.baseURL = '/api';這樣,我們請求接口就可以直接this.$axios.get('app.php?m=App&c=Index&a=index'),很簡單有木有。此時(shí)如果你在network中查看xhr請求,你會發(fā)現(xiàn)顯示的是localhost:8080/api的請求地址。這樣沒什么大驚小怪的,代理而已:
給大家分享我收集整理的各種學(xué)習(xí)資料,前端小白交流、學(xué)習(xí)交流,也可以直接問我,我會組織大家一起做項(xiàng)目練習(xí),幫助大家匹配一位學(xué)習(xí)伙伴互相監(jiān)督學(xué)習(xí)-下面是學(xué)習(xí)資料參考。






