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